package thinlet.midp;

/*
 * Thinlet GUI toolkit - www.thinlet.com Copyright (C) 2002-2003 Robert Bajzat
 * (robert.bajzat@thinlet.com)
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 0212111-1307 USA
 * 
 * 
 * 
 * SPECIAL VERSION for MIDP by Porsche-Engineering Services 
 * http://www.porsche-engineering.de
 * 
 * 
 * $Revision: 2 $
 * $Date: 2008-11-16 21:45:01 +0000 (Sun, 16 Nov 2008) $
 * $Author: m4rlonj $
 */

import java.io.*;
import java.util.*;
import javax.microedition.lcdui.*;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.TextField;
import javax.microedition.midlet.MIDlet;

class Color {

	public static int getBlue(int color) {
		return (color & 0x0000ff);
	}

	public static int getGreen(int color) {
		return (color & 0x00ff00) >> 8;
	}
	// only static methods here

	public static int getRed(int color) {
		return (color & 0xff0000) >> 16;
	}

}

class Dimension {
	public int width, height;

	public Dimension() {
		width = 0;
		height = 0;
	}

	public Dimension(Dimension d) {
		this.width = d.width;
		this.height = d.height;
	}

	public Dimension(int width, int height) {
		this.width = width;
		this.height = height;
	}
}

class KeyEvent {

	public static int VK_A = 0x41;

	public static int VK_BACK_SPACE = '\b';

	public static int VK_C = 0x43;

	public static int VK_DELETE = 0x7f;

	public static int VK_DOWN = -2;

	public static int VK_END = 0x23;

	public static int VK_ENTER = '\n';

	public static int VK_ESCAPE = 0x1b;

	public static int VK_F10 = 0x79;

	public static int VK_F6 = 0x75;

	public static int VK_F7 = 0x76;

	public static int VK_F8 = 0x77;

	public static int VK_HOME = 0x24;

	public static int VK_LEFT = -3;

	public static int VK_PAGE_DOWN = 0x22;

	//public static int VK_UP = 0x26;
	//public static int VK_DOWN = 0x28;
	//public static int VK_RIGHT = 0x27;
	//public static int VK_LEFT = 0x25;
	public static int VK_PAGE_UP = 0x21;

	public static int VK_RIGHT = -4;

	public static int VK_SPACE = 0x20;
	public static int VK_TAB = '\t';

	public static int VK_UP = -1;

	public static int VK_V = 0x56;

	public static int VK_X = 0x58;
}

class Rectangle {
	public int x, y, width, height;

	public Rectangle() {
		x = 0;
		y = 0;
		width = 0;
		height = 0;
	}

	public Rectangle(int x, int y, int width, int height) {
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
	}

	public boolean contains(int rx, int ry) {
		if ((x <= rx) && (x + width >= rx) && (y <= ry) && (y + height >= ry))
			return true;
		return false;
	}

	public boolean contains(Rectangle r) {
		if ((x <= r.x) && ((x + width) >= (r.x + r.width)) && (y <= r.y)
				&& ((y + height) >= (r.y + r.height))) {
			return true;
		}
		return false;
	}
}

/**
 * This is a copy of the StringTokenizer in JDK 1.4.2.
 *  
 */

class StringTokenizer {
	private int currentPosition;

	private String delimiters;

	private boolean delimsChanged;

	/**
	 * maxDelimChar stores the value of the delimiter character with the highest
	 * value. It is used to optimize the detection of delimiter characters.
	 */
	private char maxDelimChar;

	private int maxPosition;

	private int newPosition;

	private boolean retDelims;

	private String str;

	/**
	 * Constructs a string tokenizer for the specified string. The tokenizer
	 * uses the default delimiter set, which is
	 * <code>"&nbsp;&#92;t&#92;n&#92;r&#92;f"</code>: the space character,
	 * the tab character, the newline character, the carriage-return character,
	 * and the form-feed character. Delimiter characters themselves will not be
	 * treated as tokens.
	 * 
	 * @param str
	 *            a string to be parsed.
	 */
	public StringTokenizer(String str) {
		this(str, " \t\n\r\f", false);
	}

	/**
	 * Constructs a string tokenizer for the specified string. The characters in
	 * the <code>delim</code> argument are the delimiters for separating
	 * tokens. Delimiter characters themselves will not be treated as tokens.
	 * 
	 * @param str
	 *            a string to be parsed.
	 * @param delim
	 *            the delimiters.
	 */
	public StringTokenizer(String str, String delim) {
		this(str, delim, false);
	}

	/**
	 * Constructs a string tokenizer for the specified string. All characters in
	 * the <code>delim</code> argument are the delimiters for separating
	 * tokens.
	 * <p>
	 * If the <code>returnDelims</code> flag is <code>true</code>, then the
	 * delimiter characters are also returned as tokens. Each delimiter is
	 * returned as a string of length one. If the flag is <code>false</code>,
	 * the delimiter characters are skipped and only serve as separators between
	 * tokens.
	 * <p>
	 * Note that if <tt>delim</tt> is <tt>null</tt>, this constructor does
	 * not throw an exception. However, trying to invoke other methods on the
	 * resulting <tt>StringTokenizer</tt> may result in a
	 * <tt>NullPointerException</tt>.
	 * 
	 * @param str
	 *            a string to be parsed.
	 * @param delim
	 *            the delimiters.
	 * @param returnDelims
	 *            flag indicating whether to return the delimiters as tokens.
	 */
	public StringTokenizer(String str, String delim, boolean returnDelims) {
		currentPosition = 0;
		newPosition = -1;
		delimsChanged = false;
		this.str = str;
		maxPosition = str.length();
		delimiters = delim;
		retDelims = returnDelims;
		setMaxDelimChar();
	}

	/**
	 * Calculates the number of times that this tokenizer's
	 * <code>nextToken</code> method can be called before it generates an
	 * exception. The current position is not advanced.
	 * 
	 * @return the number of tokens remaining in the string using the current
	 *         delimiter set.
	 * @see StringTokenizer#nextToken()
	 */
	public int countTokens() {
		int count = 0;
		int currpos = currentPosition;
		while (currpos < maxPosition) {
			currpos = skipDelimiters(currpos);
			if (currpos >= maxPosition)
				break;
			currpos = scanToken(currpos);
			count++;
		}
		return count;
	}

	/**
	 * Returns the same value as the <code>hasMoreTokens</code> method. It
	 * exists so that this class can implement the <code>Enumeration</code>
	 * interface.
	 * 
	 * @return <code>true</code> if there are more tokens; <code>false</code>
	 *         otherwise.
	 * @see java.util.Enumeration
	 * @see StringTokenizer#hasMoreTokens()
	 */
	public boolean hasMoreElements() {
		return hasMoreTokens();
	}

	/**
	 * Tests if there are more tokens available from this tokenizer's string. If
	 * this method returns <tt>true</tt>, then a subsequent call to
	 * <tt>nextToken</tt> with no argument will successfully return a token.
	 * 
	 * @return <code>true</code> if and only if there is at least one token in
	 *         the string after the current position; <code>false</code>
	 *         otherwise.
	 */
	public boolean hasMoreTokens() {
		/*
		 * Temporary store this position and use it in the following nextToken()
		 * method only if the delimiters have'nt been changed in that
		 * nextToken() invocation.
		 */
		newPosition = skipDelimiters(currentPosition);
		return (newPosition < maxPosition);
	}

	/**
	 * Returns the same value as the <code>nextToken</code> method, except
	 * that its declared return value is <code>Object</code> rather than
	 * <code>String</code>. It exists so that this class can implement the
	 * <code>Enumeration</code> interface.
	 * 
	 * @return the next token in the string.
	 * @exception NoSuchElementException
	 *                if there are no more tokens in this tokenizer's string.
	 * @see java.util.Enumeration
	 * @see StringTokenizer#nextToken()
	 */
	public Object nextElement() {
		return nextToken();
	}

	/**
	 * Returns the next token from this string tokenizer.
	 * 
	 * @return the next token from this string tokenizer.
	 * @exception NoSuchElementException
	 *                if there are no more tokens in this tokenizer's string.
	 */
	public String nextToken() {
		/*
		 * If next position already computed in hasMoreElements() and delimiters
		 * have changed between the computation and this invocation, then use
		 * the computed value.
		 */

		currentPosition = (newPosition >= 0 && !delimsChanged) ? newPosition
				: skipDelimiters(currentPosition);

		/* Reset these anyway */
		delimsChanged = false;
		newPosition = -1;

		if (currentPosition >= maxPosition)
			throw new NoSuchElementException();
		int start = currentPosition;
		currentPosition = scanToken(currentPosition);
		return str.substring(start, currentPosition);
	}

	/**
	 * Returns the next token in this string tokenizer's string. First, the set
	 * of characters considered to be delimiters by this
	 * <tt>StringTokenizer</tt> object is changed to be the characters in the
	 * string <tt>delim</tt>. Then the next token in the string after the
	 * current position is returned. The current position is advanced beyond the
	 * recognized token. The new delimiter set remains the default after this
	 * call.
	 * 
	 * @param delim
	 *            the new delimiters.
	 * @return the next token, after switching to the new delimiter set.
	 * @exception NoSuchElementException
	 *                if there are no more tokens in this tokenizer's string.
	 */
	public String nextToken(String delim) {
		delimiters = delim;

		/* delimiter string specified, so set the appropriate flag. */
		delimsChanged = true;

		setMaxDelimChar();
		return nextToken();
	}

	/**
	 * Skips ahead from startPos and returns the index of the next delimiter
	 * character encountered, or maxPosition if no such delimiter is found.
	 */
	private int scanToken(int startPos) {
		int position = startPos;
		while (position < maxPosition) {
			char c = str.charAt(position);
			if ((c <= maxDelimChar) && (delimiters.indexOf(c) >= 0))
				break;
			position++;
		}
		if (retDelims && (startPos == position)) {
			char c = str.charAt(position);
			if ((c <= maxDelimChar) && (delimiters.indexOf(c) >= 0))
				position++;
		}
		return position;
	}

	/**
	 * Set maxDelimChar to the highest char in the delimiter set.
	 */
	private void setMaxDelimChar() {
		if (delimiters == null) {
			maxDelimChar = 0;
			return;
		}

		char m = 0;
		for (int i = 0; i < delimiters.length(); i++) {
			char c = delimiters.charAt(i);
			if (m < c)
				m = c;
		}
		maxDelimChar = m;
	}

	/**
	 * Skips delimiters starting from the specified position. If retDelims is
	 * false, returns the index of the first non-delimiter character at or after
	 * startPos. If retDelims is true, startPos is returned.
	 */
	private int skipDelimiters(int startPos) {
		if (delimiters == null)
			throw new NullPointerException();

		int position = startPos;
		while (!retDelims && position < maxPosition) {
			char c = str.charAt(position);
			if ((c > maxDelimChar) || (delimiters.indexOf(c) < 0))
				break;
			position++;
		}
		return position;
	}
}

/**
 * <code>Thinlet</code> special version for MIDP.
 * 
 * @author Porsche-Engineering Services
 */
public class Thinlet extends Canvas implements CommandListener, Runnable {

	private static final int DRAG_ENTERED = 6;

	private static final int DRAG_EXITED = 7;

	private static Object[] dtd;

	private static final Command editCommand = new Command("Edit",
			Command.SCREEN, 0);

	private static int evm = 0;

	private static final Boolean FALSE = new Boolean(false);

	private static Hashtable langResource = null; // for I18N

	private static Hashtable langResourceDefault = null; // for I18N

	private static final int MOUSE_DRAGGED = 4;

	private static final int MOUSE_ENTERED = 1;

	private static final int MOUSE_EXITED = 2; //

	private static final int MOUSE_PRESSED = 3;

	private static final int MOUSE_RELEASED = 5;

	private static final Command nextcommand = new Command("Next",
			Command.SCREEN, 0);

	private static final Command okCommand = new Command("OK", Command.OK, 0);
	private static final Boolean TRUE = new Boolean(true);
	static {
		Integer integer_1 = new Integer(-1);
		Integer integer0 = new Integer(0);
		Integer integer1 = new Integer(1);
		String[] orientation = { "horizontal", "vertical" };
		String[] leftcenterright = { "left", "center", "right" };
		String[] selections = { "single", "interval", "multiple" }; //+none
		dtd = new Object[] {
				"component",
				null,
				new Object[][] {
						{ "string", "name", null, null },
						{ "boolean", "enabled", "paint", TRUE },
						{ "boolean", "visible", "parent", TRUE },
						{ "boolean", "i18n", "validate", FALSE }, // for I18N
						{ "string", "tooltip", null, null },
						{ "font", "font", "validate", null },
						{ "color", "foreground", "paint", null },
						{ "color", "background", "paint", null },
						{ "integer", "width", "validate", integer0 },
						{ "integer", "height", "validate", integer0 },
						{ "integer", "colspan", "validate", integer1 },
						{ "integer", "rowspan", "validate", integer1 },
						{ "integer", "weightx", "validate", integer0 },
						{ "integer", "weighty", "validate", integer0 },
						{
								"choice",
								"halign",
								"validate",
								new String[] { "fill", "center", "left",
										"right" } },
						{
								"choice",
								"valign",
								"validate",
								new String[] { "fill", "center", "top",
										"bottom" } },
						// component class String null*
						// parent Object null
						// (bounds) Rectangle 0 0 0 0
						{ "property", "property", null, null },
						{ "method", "init" }, { "method", "focuslost" },
						{ "method", "focusgained" } },
				"label",
				"component",
				new Object[][] { { "string", "text", "validate", null },
						{ "icon", "icon", "validate", null },
						{ "choice", "alignment", "validate", leftcenterright },
						{ "integer", "mnemonic", "paint", integer_1 },
						{ "component", "for", null, null } },
				"button",
				"label",
				new Object[][] {
						{ "choice", "alignment", "validate",
								new String[] { "center", "left", "right" } },
						{ "method", "action" },
						{
								"choice",
								"type",
								"paint",
								new String[] { "normal", "default", "cancel",
										"link" } } },
				"checkbox",
				"label",
				new Object[][] { { "boolean", "selected", "paint", FALSE }, //...group
						{ "string", "group", "paint", null }, //...group
						{ "method", "action" } },
				"togglebutton",
				"checkbox",
				null,
				"combobox",
				"textfield",
				new Object[][] { { "icon", "icon", "validate", null },
						{ "integer", "selected", "layout", integer_1 } },
				"choice",
				null,
				new Object[][] { { "string", "name", null, null },
						{ "boolean", "enabled", "paint", TRUE },
						{ "boolean", "i18n", "validate", FALSE }, // for I18N
						{ "string", "text", "parent", null },
						{ "icon", "icon", "parent", null },
						{ "choice", "alignment", "parent", leftcenterright },
						{ "string", "tooltip", null, null },
						{ "font", "font", "validate", null },
						{ "color", "foreground", "paint", null },
						{ "color", "background", "paint", null },
						{ "property", "property", null, null } },
				"textfield",
				"component",
				new Object[][] { { "string", "text", "layout", "" },
						{ "integer", "columns", "validate", integer0 },
						{ "choice", "alignment", "validate", leftcenterright },
						{ "boolean", "editable", "paint", TRUE },
						{ "integer", "start", "layout", integer0 },
						{ "integer", "end", "layout", integer0 },
						{ "method", "action" }, { "method", "insert" },
						{ "method", "remove" }, { "method", "caret" },
						{ "method", "perform" } },
				"passwordfield",
				"textfield",
				null,
				"textarea",
				"textfield",
				new Object[][] { { "integer", "rows", "validate", integer0 },
						{ "boolean", "border", "validate", TRUE },
						{ "boolean", "wrap", "layout", FALSE } },
				"tabbedpane",
				"component",
				new Object[][] {
						{
								"choice",
								"placement",
								"validate",
								new String[] { "top", "left", "bottom",
										"right", "stacked" } },
						{ "integer", "selected", "paint", integer0 },
						{ "method", "action" } }, //...focus
				"tab",
				"choice",
				new Object[][] { { "integer", "mnemonic", "paint", integer_1 } },
				"panel",
				"component",
				new Object[][] {
						{ "integer", "columns", "validate", integer0 },
						{ "integer", "top", "validate", integer0 },
						{ "integer", "left", "validate", integer0 },
						{ "integer", "bottom", "validate", integer0 },
						{ "integer", "right", "validate", integer0 },
						{ "integer", "gap", "validate", integer0 },
						{ "string", "text", "validate", null },
						{ "icon", "icon", "validate", null },
						{ "boolean", "border", "validate", FALSE },
						{ "boolean", "scrollable", "validate", FALSE } },
				"desktop",
				"component",
				null,
				"dialog",
				"panel",
				new Object[][] { { "boolean", "modal", null, FALSE },
						{ "boolean", "resizable", null, FALSE },
						{ "method", "close" },
						{ "boolean", "maximizable", "paint", FALSE },
						{ "boolean", "iconifiable", "paint", FALSE } },
				"spinbox",
				"textfield",
				new Object[][] {
						{ "integer", "minimum", null,
								new Integer(Integer.MIN_VALUE) },
						{ "integer", "maximum", null,
								new Integer(Integer.MAX_VALUE) },
						{ "integer", "step", null, integer1 },
						{ "integer", "value", null, integer0 } }, // ==
				"progressbar",
				"component",
				new Object[][] {
						{ "choice", "orientation", "validate", orientation },
						{ "integer", "minimum", "paint", integer0 }, //...checkvalue
						{ "integer", "maximum", "paint", new Integer(100) },
						{ "integer", "value", "paint", integer0 } },
				// change stringpainted
				"slider",
				"progressbar",
				new Object[][] { { "integer", "unit", null, new Integer(5) },
						{ "integer", "block", null, new Integer(25) },
						{ "method", "action" } },
				// minor/majortickspacing
				// inverted
				// labelincrement labelstart
				"splitpane",
				"component",
				new Object[][] {
						{ "choice", "orientation", "validate", orientation },
						{ "integer", "divider", "layout", integer_1 } },
				"list",
				"component",
				new Object[][] {
						{ "choice", "selection", "paint", selections },
						{ "method", "action" }, { "method", "perform" },
						{ "boolean", "line", "validate", TRUE } },
				"item",
				"choice",
				new Object[][] { { "boolean", "selected", null, FALSE } },
				"table",
				"list",
				new Object[][] {
				/*
				 * { "choice", "selection", new String[] { "singlerow",
				 * "rowinterval", "multiplerow", "cell", "cellinterval",
				 * "singlecolumn", "columninterval", "multiplecolumn" } }
				 */},
				"header",
				null,
				new Object[][] { { "method", "action" },
						{ "boolean", "resizable", null, TRUE } },
				// reordering allowed
				// autoresize mode: off next (column boundries) subsequents last
				// all
				// columns
				// column row selection
				// selection row column cell
				// editing row/column
				"column",
				"choice",
				new Object[][] {
						{ "integer", "width", null, new Integer(80) },
						{ "choice", "sort", null,
								new String[] { "none", "ascent", "descent" } },
						{ "boolean", "selected", null, FALSE } },
				"row",
				null,
				new Object[][] { { "boolean", "selected", null, FALSE } },
				"cell",
				"choice",
				null,
				"tree",
				"list",
				new Object[][] { { "boolean", "angle", null, FALSE },
						{ "method", "expand" }, { "method", "collapse" } },
				"node",
				"choice",
				new Object[][] { { "boolean", "selected", null, FALSE },
						{ "boolean", "expanded", null, TRUE } },
				"separator",
				"component",
				null,
				"menubar",
				"component",
				new Object[][] { { "choice", "placement", "validate",
						new String[] { "top", "bottom" } } },
				"menu",
				"choice",
				new Object[][] { { "integer", "mnemonic", "paint", integer_1 } },
				"menuitem",
				"choice",
				new Object[][] { { "keystroke", "accelerator", null, null },
						{ "method", "action" },
						{ "integer", "mnemonic", "paint", integer_1 } },
				"checkboxmenuitem", "menuitem",
				new Object[][] { { "boolean", "selected", "paint", FALSE }, //...group
						{ "string", "group", "paint", null } }, //...group
				"popupmenu", "component",
				new Object[][] { { "method", "menushown" } }, // Post
				// menu:
				// Shift+F10
				"bean", "component",
				new Object[][] { { "bean", "bean", null, null } } };
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Creates a new component
	 * 
	 * @param classname
	 *            the widget type (e.g. <i>button </i>)
	 * @return a new component, every component is simply an <i>Object </i>
	 * @throws java.lang.IllegalArgumentException
	 *             for unknown widget type
	 */
	public static Object create(String classname) { //#
		for (int i = 0; i < dtd.length; i += 3) {
			if (dtd[i].equals(classname)) {
				return createImpl((String) dtd[i]);
			}
		}
		throw new IllegalArgumentException("unknown " + classname);
	}

	/**
	 *  
	 */
	private static Object createImpl(String classname) {
		return new Object[] { ":class", classname, null };
	}

	/**
	 * @param text
	 * @param multiline
	 * @return
	 */
	private static String filter(String text, boolean multiline) {
		StringBuffer filtered = new StringBuffer(text.length());
		for (int i = 0; i < text.length(); i++) {
			char ckey = text.charAt(i);
			if (((ckey > 0x1f) && (ckey < 0x7f))
					|| ((ckey > 0x9f) && (ckey < 0xffff))
					|| (multiline && (ckey == '\n'))) {
				filtered.append(ckey);
			}
		}
		return (filtered.length() != text.length()) ? filtered.toString()
				: text;
	}

	/**
	 * Get a specified sub-component or component part. Key is e.g. "header"
	 * (table header), ":parent" (parent component), ":comp" (head of a
	 * component list), ":next" (next component in a list), "popupmenu" is a
	 * popupmenu, etc ...
	 */
	static Object get(Object component, Object key) {
		for (Object[] entry = (Object[]) component; entry != null; entry = (Object[]) entry[2]) {
			if (entry[0] == key) {
				return entry[1];
			}
		}
		return null;
	}

	/**
	 *  
	 */
	private static Object get(Object component, String key, String type) {
		Object[] definition = getDefinition(getClass(component), key, type);
		Object value = get(component, definition[1]);
		return (value != null) ? value : definition[3];
	}

	/**
	 * Gets the type of the given component
	 * 
	 * @param component
	 *            a widget
	 * @return the class name of the component (e.g. <i>button </i>)
	 */
	public static String getClass(Object component) { //#
		return (String) get(component, ":class");
	}

	/**
	 * 
	 * @throws java.lang.IllegalArgumentException
	 */
	private static Object[] getDefinition(Object classname, String key,
			String type) {
		Object currentname = classname;
		while (classname != null) {
			for (int i = 0; i < dtd.length; i += 3) {
				if (dtd[i] == classname) {
					Object[][] attributes = (Object[][]) dtd[i + 2];
					if (attributes != null) {
						for (int j = 0; j < attributes.length; j++) {
							if (attributes[j][1].equals(key)) {
								if ((type != null)
										&& (type != attributes[j][0])) {
									throw new IllegalArgumentException(
											attributes[j][0].toString());
								}
								return attributes[j];
							}
						}
					}
					classname = dtd[i + 1];
					break;
				}
			}
		}
		throw new IllegalArgumentException("unknown " + key + " " + type
				+ " for " + currentname);
	}

	/**
	 * Gets the attribute value by the specified key for a DOM tag
	 * 
	 * @param node
	 *            a specified tag
	 * @param key
	 *            a string to identify the value pair
	 * @return the value, or null
	 */
	protected static String getDOMAttribute(Object node, String key) {
		return (String) get(node, key);
	}

	/**
	 * Gets the number of tags in a tag by a specified tagname
	 * 
	 * @param node
	 *            a specified tag
	 * @param key
	 *            the searched tagname
	 * @return the number of tags
	 */
	protected static int getDOMCount(Object node, String key) {
		return getItemCountImpl(node, key);
	}

	/**
	 * Gets the subtag of the specified tag by tagname and index
	 * 
	 * @param node
	 *            a specified tag
	 * @param key
	 *            the searched tagname
	 * @param index
	 *            the index of the requested subtag
	 * @return the found tag, or null
	 */
	protected static Object getDOMNode(Object node, String key, int index) {
		return getItemImpl(node, key, index);
	}

	/**
	 * Gets the content string of a tag
	 * 
	 * @param node
	 *            a specified tag
	 * @return the value, or null
	 */
	protected static String getDOMText(Object node) {
		return (String) get(node, ":text");
	}

	/**
	 *  
	 */
	private static int getItemCountImpl(Object component, String key) {
		int i = 0;
		for (Object comp = get(component, key); comp != null; comp = get(comp,
				":next")) {
			i++;
		}
		return i;
	}

	/**
	 * Referenced by DOM, replace by getItem for others
	 */
	private static Object getItemImpl(Object component, Object key, int index) {
		int i = 0;
		for (Object item = get(component, key); item != null; item = get(item,
				":next")) {
			if (i == index) {
				return item;
			}
			i++;
		}
		return null;
	}

	/**
	 * Returns language resource bundle currently in use, or default bundle, or
	 * null.
	 */
	public static Hashtable getLangResource() { // for I18N
		return langResource;
	}

	/**
	 * Returns default language resource bundle, or null.
	 */
	public static Hashtable getLangResourceDefault() { // for I18N
		return langResourceDefault;
	}

	/**
	 *  
	 */
	static boolean set(Object component, Object key, Object value) {
		Object[] previous = (Object[]) component;
		for (Object[] entry = previous; entry != null; entry = (Object[]) entry[2]) {
			if (entry[0] == key) {
				if (value != null) { // set the row's value
					Object oldvalue = entry[1];
					entry[1] = value;
					return !value.equals(oldvalue);
				} else { // remove the row
					previous[2] = entry[2];
					entry[2] = null;
					return true;
				}
			}
			previous = entry;
		}
		if (value != null) { // append a new row
			previous[2] = new Object[] { key, value, null };
			return true;
		}
		return false;
	}

	private transient boolean allI18n = false; // for I18N

	public Object[] args;

	protected transient int block;

	private transient int c_bg;

	private transient int c_border;

	private transient int c_ctrl;

	private transient int c_disable;

	private transient int c_focus;

	private transient int c_hover;

	private transient int c_press;

	private transient int c_select;

	private transient int c_text;

	private transient int c_textbg;

	private transient String clipboard;

	private Object content = createImpl("desktop");

	public Object[] data;

	// enter the starting characters of a list item text within a short time to
	// select the first matching item
	private transient String findprefix = "";

	private transient long findtime;

	private transient boolean focusinside = true;

	public transient Object focusowner;

	private transient Font font;

	private transient Image hgradient, vgradient;

	private TextBox input;

	private transient Object insidepart;

	private MIDlet midlet;

	private transient Object mouseinside;

	private transient Object mousepressed;

	private transient int mousex, mousey;

	private transient Object popupowner;

	private transient Object pressedpart;

	private transient int referencex, referencey;

	private transient Hashtable resourcebundle; // for internationalization

	private transient Thread timer;

	private transient Object tooltipowner;

	private transient long watch;

	private transient long watchdelay;

	public Thinlet(MIDlet midlet)

	{

		super();
		//setFullScreenMode(true);
		// sizeChanged(200,320);
		font = Font
				.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_SMALL);
		setFont(font);
		setColors(0xe6e6e6, 0x000000, 0xffffff, 0x909090, 0xb0b0b0, 0xededed,
				0xb9b9b9, 0x89899a, 0xa2958a);

		// EVM has larger fillRect, fillOval, and drawImage(part), others are
		// correct
		// contributed by Ibsen Ramos-Bonilla and AK
		try {
			if ((System.getProperty("java.vendor").indexOf("Insignia") != -1)
					&& System.getProperty("os.name").indexOf("Windows CE") == -1) {
				evm = -1;
			}
		} catch (Exception exc) { /* never */
		}
		this.midlet = midlet;
		addCommand(nextcommand);
		//addCommand(prevcommand);
		setCommandListener(this);
	}

	/**
	 * Adds the specified component to the root desktop
	 * 
	 * @param component
	 *            a widget to be added
	 */
	public void add(Object component) {
		add(content, component, 0);
	}

	/**
	 * Adds the specified component to the end of the specified container
	 * 
	 * @param parent
	 *            a container
	 * @param component
	 *            a component to be added
	 */
	public void add(Object parent, Object component) {
		add(parent, component, -1);
	}

	/**
	 * Adds the specified component to the container at the given position
	 * 
	 * @param parent
	 *            a container
	 * @param component
	 *            a component to be inserted
	 * @param index
	 *            the position at which to insert the component, or -1 to insert
	 *            the component at the end
	 */
	public void add(Object parent, Object component, int index) {
		addImpl(parent, component, index);
		update(component, "validate");
		if (parent == content) {
			setNextFocusable(component, false);
		}
	}

	/**
	 * 
	 * @throws java.lang.IllegalArgumentException
	 */
	private void addAttribute(Object component, String key, String value,
			Vector lasts) {
		// replace value found in the bundle
		if ((resourcebundle != null) && value.startsWith("i18n.")) {
			value = (String) resourcebundle.get(value.substring(5));
		}

		Object[] definition = getDefinition(getClass(component), key, null);
		key = (String) definition[1];
		if ("string" == definition[0]) {
			setString(component, key, value, (String) definition[3]);
		} else if ("choice" == definition[0]) {
			String[] values = (String[]) definition[3];
			setChoice(component, key, value, values, values[0]);
		} else if ("boolean" == definition[0]) {
			if ("true".equals(value)) {
				if (definition[3] == FALSE) {
					set(component, key, TRUE);
				}
			} else if ("false".equals(value)) {
				if (definition[3] == TRUE) {
					set(component, key, FALSE);
				}
			} else
				throw new IllegalArgumentException(value);
		} else if ("integer" == definition[0]) {
			set(component, key, Integer.valueOf(value));
		} else if ("icon" == definition[0]) {
			set(component, key, getIcon(value));
		} else if (("method" == definition[0])
				|| ("component" == definition[0])) {
			lasts.addElement(component);
			lasts.addElement(definition);
			lasts.addElement(value);
		} else if ("property" == definition[0]) {
			StringTokenizer st = new StringTokenizer(value, ";");
			while (st.hasMoreTokens()) {
				String token = st.nextToken();
				int equals = token.indexOf('=');
				if (equals == -1) {
					throw new IllegalArgumentException(token);
				}
				putProperty(component, token.substring(0, equals), token
						.substring(equals + 1));
			}
		} else if ("font" == definition[0]) {
			String name = null;
			boolean bold = false;
			boolean italic = false;
			int size = 0;
			StringTokenizer st = new StringTokenizer(value);
			while (st.hasMoreTokens()) {
				String token = st.nextToken();
				if ("bold".equals(token.toLowerCase())) {
					bold = true;
				} else if ("italic".equals(token.toLowerCase())) {
					italic = true;
					font = Font.getFont(Font.STYLE_ITALIC);
				} else {
					try {
						size = Integer.parseInt(token);
						if (size <= 9) {
							//  font = Font.getFont(Font.SIZE_SMALL);
						} else if ((size > 9) && (size <= 14)) {
							//  font = Font.getFont(Font.SIZE_MEDIUM);
						} else if (size > 14) {
							//  font = Font.getFont(Font.SIZE_LARGE);
						}
					} catch (NumberFormatException nfe) {
						name = (name == null) ? token : (name + " " + token);
					}
				}
			}
			if (name == null) {
				name = "System";
				//name = font.getName();
			}
			int face = getFontFace(name);
			if (size == 0) {
				size = font.getSize();
			}
			size = 8;
			set(component, key, Font.getFont(face, Font.STYLE_BOLD, size));
		} else if ("color" == definition[0]) {
			int color = 0;
			if (value.startsWith("#")) {
				color = Integer.parseInt(value.substring(1), 16);
			} else if (value.startsWith("0x")) {
				color = Integer.parseInt(value.substring(2), 16);
			} else { // three separated integer including red, green, and blue
				StringTokenizer st = new StringTokenizer(value, " \r\n\t,");
				color = 0xff000000
						| ((Integer.parseInt(st.nextToken()) & 0xff) << 16)
						| ((Integer.parseInt(st.nextToken()) & 0xff) << 8)
						| (Integer.parseInt(st.nextToken()) & 0xff);
			}
			set(component, key, new Integer(color));
		} else if ("keystroke" == definition[0]) {
			setKeystrokeImpl(component, key, value);
		} else if ("bean" == definition[0]) {
			throw new IllegalArgumentException("bean not supported");
		} else
			throw new IllegalArgumentException((String) definition[0]);
	}

	/**
	 *  
	 */
	private Object addElement(Object parent, String name) {
		Object component = create(name);
		addImpl(parent, component, -1);
		return component;
	}

	/**
	 * Add the component to the parent's ':comp' list, and set its ':parent' or
	 * set single components
	 * 
	 * @param index
	 *            add at the specified index
	 * @throws java.lang.IllegalArgumentException
	 */
	private void addImpl(Object parent, Object component, int index) {
		String parentclass = getClass(parent);
		String classname = getClass(component);
		if ((("combobox" == parentclass) && ("choice" == classname))
				|| (("tabbedpane" == parentclass) && ("tab" == classname))
				|| (("list" == parentclass) && ("item" == classname))
				|| (("table" == parentclass) && ("row" == classname))
				|| (("header" == parentclass) && ("column" == classname))
				|| (("row" == parentclass) && ("cell" == classname))
				|| ((("tree" == parentclass) || ("node" == parentclass)) && ("node" == classname))
				|| (("menubar" == parentclass) && ("menu" == classname))
				|| ((("menu" == parentclass) || ("popupmenu" == parentclass)) && (("menu" == classname)
						|| ("menuitem" == classname)
						|| ("checkboxmenuitem" == classname) || ("separator" == classname)))
				|| ((("panel" == parentclass) || ("desktop" == parentclass)
						|| ("splitpane" == parentclass)
						|| ("dialog" == parentclass) || ("tab" == parentclass))
						&& instance(classname, "component") && (classname != "popupmenu"))) {
			insertItem(parent, ":comp", component, index);
			set(component, ":parent", parent);
		} else if ((("table" == parentclass) && ("header" == classname))
				|| (("popupmenu" == classname) && instance(parentclass,
						"component"))) {
			set(parent, classname, component);
			set(component, ":parent", parent);
		} else
			throw new IllegalArgumentException(classname + " add "
					+ parentclass);
	}

	/**
	 *  
	 */
	private boolean changeCheck(Object component, boolean box) {
		String group = getString(component, "group", null);
		if (group != null) {
			if (getBoolean(component, "selected", false)) {
				return false;
			}
			for (Object comp = get(getParent(component), ":comp"); comp != null; comp = get(
					comp, ":next")) {
				if (comp == component) {
					setBoolean(component, "selected", true);
				} else if (group.equals(get(comp, "group"))
						&& getBoolean(comp, "selected", false)) {
					setBoolean(comp, "selected", false);
					if (box) {
						repaint(comp);
					} //checkbox only
				}
			}
		} else {
			setBoolean(component, "selected", !getBoolean(component,
					"selected", false), false);
		}
		invoke(component, null, "action");
		return true;
	}

	/**
	 * @param component
	 *            a textfield, passwordfield, textarea, combobox, or spinbox
	 * @param text
	 *            current text
	 * @param insert
	 *            a string to replace thr current selection
	 * @param movestart
	 *            new selection start position
	 * @param moveend
	 *            new caret (selection end) position
	 * @param start
	 *            current selection start position
	 * @param end
	 *            current caret position
	 * @return true if selection, caret location, or text content changed
	 */
	private boolean changeField(Object component, String text, String insert,
			int movestart, int moveend, int start, int end) {
		movestart = Math.max(0, Math.min(movestart, text.length()));
		moveend = Math.max(0, Math.min(moveend, text.length()));
		if ((insert == null) && (start == movestart) && (end == moveend)) {
			return false;
		}
		if (insert != null) {
			int min = Math.min(movestart, moveend);
			set(component, "text", text.substring(0, min) + insert
					+ text.substring(Math.max(movestart, moveend)));
			movestart = moveend = min + insert.length();
			invoke(component, null, "action"); // deprecated
		}
		if (start != movestart) {
			setInteger(component, "start", movestart, 0);
		}
		if (end != moveend) {
			setInteger(component, "end", moveend, 0);
		}
		validate(component);
		invoke(
				component,
				null,
				(insert != null) ? ((insert.length() > 0) ? "insert" : "remove")
						: "caret");
		return true;
	}

	/**
	 * The SAX-like parser calls this method, you have to overwrite it
	 * 
	 * @param text
	 *            the content of a tag
	 */
	protected void characters(String text) {
	}

	/**
	 * Check the previous mouse location again because of a possible layout
	 * change
	 */
	private void checkLocation() {
		findComponent(content, mousex, mousey);
		handleMouseEvent(mousex, mousex, 1, false, false, false, MOUSE_ENTERED,
				mouseinside, insidepart);
	}

	/**
	 * mnemonic (e.g. Alt-X): - check: label, button, checkbox, togglebutton,
	 * menubar menus, tabbedpane tabs - path: panel, desktop, dialog, splitpane
	 * components, tabbedpane selected component accelerator (e.g. Ctrl-Shift-X,
	 * F4): - check: menuitem, checkboxmenuitem - path: see above, and menubar,
	 * and menu items menubar F10: check menubar only button enter, escape:
	 * check button only
	 * 
	 * @param component
	 * @param parent
	 *            check upwards if true
	 * @param checked
	 *            this leaf is already checked
	 * @param keycode
	 * @param modifiers
	 * @return true if the char was consumed
	 */
	private boolean checkMnemonic(Object component, boolean parent,
			Object checked, int keycode, int modifiers) {
		if ((component == null) || !getBoolean(component, "visible", true)
				|| !getBoolean(component, "enabled", true)) { //+
			// enabled
			// comp
			// in
			// disabled parent
			return false;
		}
		String classname = getClass(component);
		if ("label" == classname) {
			if (hasMnemonic(component, keycode, modifiers)) {
				Object labelfor = get(component, "for");
				if (labelfor != null) {
					requestFocus(labelfor);
					return true;
				}
			}
		} else if ("button" == classname) {
			if (((modifiers == 0) && (((keycode == KeyEvent.VK_ENTER) && (get(
					component, "type") == "default")) || ((keycode == KeyEvent.VK_ESCAPE) && (get(
					component, "type") == "cancel"))))
					|| hasMnemonic(component, keycode, modifiers)) {
				invoke(component, null, "action");
				repaint(component);
				return true;
			}
		} else if (("checkbox" == classname) || ("togglebutton" == classname)) {
			if (hasMnemonic(component, keycode, modifiers)) {
				changeCheck(component, true);
				repaint(component);
				return true;
			}
		} else if ("menubar" == classname) {
			for (Object menu = get(component, ":comp"); menu != null; menu = get(
					menu, ":next")) {
				if (hasMnemonic(menu, keycode, modifiers)
						|| ((modifiers == 0) && (keycode == KeyEvent.VK_F10))) {
					closeup();
					set(component, "selected", menu);
					popupMenu(component);
					repaint(component, "menubar", menu);
					return true;
				}
			}
		} else if (("menuitem" == classname)
				|| ("checkboxmenuitem" == classname)) {
			if (hasAccelerator(component, keycode, modifiers)) {
				invoke(component, null, "action");
			}
		} else if ("tabbedpane" == classname) {
			int selected = getInteger(component, "selected", 0);
			int i = 0;
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				if (hasMnemonic(tab, keycode, modifiers)) {
					if (selected != i) {
						setInteger(component, "selected", i, 0);
						repaint(component);
						invoke(component, getItem(component, i), "action");
					}
					return true;
				}
				i++;
			}
			Object comp = get(getItem(component, selected), ":comp");
			if ((comp != null) && (comp != checked)
					&& checkMnemonic(comp, false, null, keycode, modifiers)) {
				return true;
			}
		}
		// check subcomponents
		if (("panel" == classname) || ("desktop" == classname)
				|| ("dialog" == classname) || ("splitpane" == classname)
				|| ("menubar" == classname) || ("menu" == classname)) {
			for (Object comp = get(component, ":comp"); comp != null; comp = get(
					comp, ":next")) {
				if ((comp != checked)
						&& checkMnemonic(comp, false, null, keycode, modifiers)) {
					return true;
				}
			}
		}
		// check parent
		if (parent
				&& (("dialog" != classname) || !getBoolean(component, "modal",
						false))) {
			if (checkMnemonic(getParent(component), true,
					("tab" == classname) ? checked : component, keycode,
					modifiers)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Scroll tabs to make the selected one visible
	 * 
	 * @param component
	 *            a tabbedpane
	 */
	private void checkOffset(Object component) {
		String placement = getString(component, "placement", "top");
		int selected = getInteger(component, "selected", 0);
		int i = 0;
		if (placement == "stacked") {
			int dy = 0;
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				Rectangle r = getRectangle(tab, "bounds");
				r.y = dy;
				dy += r.height;
				if (i == selected) {
					dy += getRectangle(get(tab, ":comp"), "bounds").height + 2;
				}
				i++;
			}
			if (mouseinside == component) { // layout changed, check the hovered
				// tab
				checkLocation();
			}
			return;
		}
		boolean horizontal = ((placement == "top") || (placement == "bottom"));
		Rectangle bounds = getRectangle(component, "bounds");
		int panesize = horizontal ? bounds.width : bounds.height;
		int first = 0;
		int last = 0;
		int d = 0;
		for (Object tab = get(component, ":comp"); tab != null; tab = get(tab,
				":next")) {
			Rectangle r = getRectangle(tab, "bounds");
			if (i == 0) {
				first = (horizontal ? r.x : r.y);
			}
			last = (horizontal ? (r.x + r.width) : (r.y + r.height));
			if (i == selected) {
				int ifrom = (horizontal ? r.x : r.y) - 6;
				int ito = (horizontal ? (r.x + r.width) : (r.y + r.height)) + 6;
				if (ifrom < 0) {
					d = -ifrom;
				} else if (ito > panesize) {
					d = panesize - ito;
				}
			}
			i++;
		}
		d = Math.min(-first, Math.max(d, panesize - last));
		if (d != 0) {
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				Rectangle r = getRectangle(tab, "bounds");
				if (horizontal) {
					r.x += d;
				} else {
					r.y += d;
				}
				Object comp = get(tab, ":comp"); // relative to the tab location
				if ((comp != null) && getBoolean(comp, "visible", true)) {
					Rectangle rc = getRectangle(comp, "bounds");
					if (horizontal) {
						rc.x -= d;
					} else {
						rc.y -= d;
					}
				}
			}
			if (mouseinside == component) { // layout changed, check the hovered
				// tab
				checkLocation();
			}
		}
	}

	/**
	 * @param item
	 *            //TODO can be scrollbar string
	 */
	private void closeCombo(Object combobox, Object combolist, Object item) {
		if ((item != null) && getBoolean(item, "enabled", true)) {
			String text = getString(item, "text", "");
			set(combobox, "text", text); // if editable
			putProperty(combobox, "i18n.text", null); // for I18N
			setInteger(combobox, "start", text.length(), 0);
			setInteger(combobox, "end", 0, 0);
			set(combobox, "icon", get(item, "icon"));
			validate(combobox);
			setInteger(combobox, "selected", getIndex(combobox, item), -1);
			invoke(combobox, item, "action");
		}
		set(combolist, "combobox", null);
		set(combobox, ":combolist", null);
		removeItemImpl(content, combolist);
		repaint(combolist);
		set(combolist, ":parent", null);
		popupowner = null;
		if (mouseinside == combolist) {
			checkLocation();
		}
	}

	/**
	 *  
	 */
	private void closeup() {
		if (popupowner != null) {
			String classname = getClass(popupowner);
			if ("menubar" == classname) {
				set(popupowner, "selected", null);
				popupMenu(popupowner);
				repaint(popupowner); // , selected
			} else if ("combobox" == classname) {
				closeCombo(popupowner, get(popupowner, ":combolist"), null);
			} else { // "popupmenu"
				popupMenu(popupowner);
			}
			popupowner = null;
		}
	}

	public void commandAction(Command command, Displayable displayable) {
		if (command == nextcommand) {
			setNextFocusable(focusowner, false);
			repaint(focusowner);
			closeup();
		} else if (command == editCommand) { /* display the high-level text box */
			// lazy instantiation of text box

			if (input == null) {

				input = new TextBox("", "", 50, TextField.ANY);
				input.addCommand(okCommand);
				input.setCommandListener(this);

				// constrain input appropriate to text component type
				if ("passwordfield" == getClass(focusowner)) {

					input.setConstraints(TextField.PASSWORD);
				} else {

					input.setConstraints(TextField.ANY);
				}
			}
			// transfer text from thinlet component to text box
			String text = getString(focusowner, "text", "");
			input.setString(text);
			// display the text box

			Display.getDisplay(this.midlet).setCurrent(input);
		} else if (command == okCommand) {/*
										   * update the thinlet text component
										   * and restore the canvas
										   */
			set(focusowner, "text", input.getString());
			Display.getDisplay(this.midlet).setCurrent(this);/*
															  * restore the
															  * display
															  */
			input = null;
		}

	}

	/**
	 * This method is called by the FrameLauncher if the window was closing, or
	 * AppletLauncher's destroy method. Overwrite it to e.g. save the
	 * application changes.
	 * 
	 * @return true to exit, and false to keep the frame and continue the
	 *         application
	 */
	public boolean destroy() {
		return true;
	}

	/**
	 *  
	 */
	private void doLayout(Object component) {
		String classname = getClass(component);
		if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)) {
				Image icon = getIcon(component, "icon", null);
				layoutField(component, block, false, (icon != null) ? icon
						.getWidth() : 0);
			} // set editable -> validate (overwrite textfield repaint)
			else {
				int selected = getInteger(component, "selected", -1);
				if (selected != -1) { //...
					Object choice = getItem(component, selected);
					set(component, "text", get(choice, "text"));
					set(component, "icon", get(choice, "icon"));
				}
			}
		} else if (("textfield" == classname) || ("passwordfield" == classname)) {
			layoutField(component, 0, ("passwordfield" == classname), 0);
		} else if ("textarea" == classname) {
			String text = getString(component, "text", "");
			int start = getInteger(component, "start", 0);
			if (start > text.length()) {
				setInteger(component, "start", start = text.length(), 0);
			}
			int end = getInteger(component, "end", 0);
			if (end > text.length()) {
				setInteger(component, "end", end = text.length(), 0);
			}

			boolean wrap = getBoolean(component, "wrap", false);
			char[] chars = null;
			if (wrap) {
				Rectangle bounds = getRectangle(component, "bounds");
				chars = getChars(component, text, true, bounds.width - 4,
						bounds.height);
				if (chars == null) { // need scrollbars
					chars = getChars(component, text, true, bounds.width
							- block - 4, 0);
				}
			} else {
				chars = getChars(component, text, false, 0, 0);
			}

			//SH
			//Font currentfont = (Font) get(component, "font");
			Font currentfont = font;
			if (currentfont == null)
				currentfont = font;
			int width = 0, height = 0;
			int caretx = 0;
			int carety = 0;
			for (int i = 0, j = 0; j <= chars.length; j++) {
				if ((j == chars.length) || (chars[j] == '\n')) {
					width = Math.max(width, currentfont.charsWidth(chars, i, j
							- i));
					if ((end >= i) && (end <= j)) {
						caretx = currentfont.charsWidth(chars, i, end - i);
						carety = height;
					}
					height += currentfont.getHeight();
					i = j + 1;
				}
			}
			layoutScroll(component, width + 2, height + 2, 0, 0, 0, 0,
					getBoolean(component, "border", true), 0);
			scrollToVisible(component, caretx, carety, 2, currentfont
					.getHeight() + 2); //?
		} else if ("tabbedpane" == classname) {
			// tabbedpane (not selected) tab padding are 1, 3, 1, and 3 pt
			Rectangle bounds = getRectangle(component, "bounds");
			String placement = getString(component, "placement", "top");
			boolean horizontal = ((placement == "top") || (placement == "bottom"));
			boolean stacked = (placement == "stacked");

			// draw up tabs in row/column
			int tabd = 0;
			Rectangle first = null; // x/y location of tab left/top
			int tabsize = 0; // max height/width of tabs
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				if ((tabd == 0)
						&& ((first = getRectangle(tab, "bounds")) != null)) {
					tabd = horizontal ? first.x : first.y; // restore previous
					// offset
				}
				Dimension d = getSize(tab, stacked ? 8 : horizontal ? 12 : 9,
						stacked ? 3 : horizontal ? 5 : 8);
				setRectangle(tab, "bounds", horizontal ? tabd : 0,
						horizontal ? 0 : tabd,
						stacked ? bounds.width : d.width, d.height);
				if (stacked) {
					tabd += d.height;
				} else {
					tabd += (horizontal ? d.width : d.height) - 3;
					tabsize = Math
							.max(tabsize, horizontal ? d.height : d.width);
				}
			}

			// match tab height/width, set tab content size
			int cx = (placement == "left") ? (tabsize + 1) : 2;
			int cy = (placement == "top") ? (tabsize + 1) : 2;
			int cwidth = bounds.width
					- ((horizontal || stacked) ? 4 : (tabsize + 3));
			int cheight = bounds.height
					- (stacked ? (tabd + 3) : (horizontal ? (tabsize + 3) : 4));
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				Rectangle r = getRectangle(tab, "bounds");
				if (!stacked) {
					if (horizontal) {
						if (placement == "bottom") {
							r.y = bounds.height - tabsize;
						}
						r.height = tabsize;
					} else {
						if (placement == "right") {
							r.x = bounds.width - tabsize;
						}
						r.width = tabsize;
					}
				}

				Object comp = get(tab, ":comp"); // relative to the tab location
				if ((comp != null) && getBoolean(comp, "visible", true)) {
					setRectangle(comp, "bounds", cx - r.x,
							stacked ? (r.height + 1) : (cy - r.y), cwidth,
							cheight);
					doLayout(comp);
				}
			}
			checkOffset(component);
		} else if (("panel" == classname) || (classname == "dialog")) {
			int gap = getInteger(component, "gap", 0);
			int[][] grid = getGrid(component);
			int top = 0;
			int left = 0;
			int contentwidth = 0;
			int contentheight = 0;
			if (grid != null) { // has subcomponents
				top = getInteger(component, "top", 0);
				left = getInteger(component, "left", 0);
				int bottom = getInteger(component, "bottom", 0);
				int right = getInteger(component, "right", 0);
				// sums the preferred size of cell widths and heights, gaps
				contentwidth = left
						+ getSum(grid[0], 0, grid[0].length, gap, false)
						+ right;
				contentheight = top
						+ getSum(grid[1], 0, grid[1].length, gap, false)
						+ bottom;
			}

			int titleheight = getSize(component, 0, 0).height; // title text and
			// icon
			setInteger(component, ":titleheight", titleheight, 0);
			boolean scrollable = getBoolean(component, "scrollable", false);
			boolean border = ("panel" == classname)
					&& getBoolean(component, "border", false);
			int iborder = (border ? 1 : 0);
			if (scrollable) { // set scrollpane areas
				if ("panel" == classname) {
					int head = titleheight / 2;
					int headgap = (titleheight > 0) ? (titleheight - head - iborder)
							: 0;
					scrollable = layoutScroll(component, contentwidth,
							contentheight, head, 0, 0, 0, border, headgap);
				} else { // dialog
					scrollable = layoutScroll(component, contentwidth,
							contentheight, 3 + titleheight, 3, 3, 3, true, 0);
				}
			}
			if (!scrollable) { // clear scrollpane bounds //+
				set(component, ":view", null);
				set(component, ":port", null);
			}

			if (grid != null) {
				int areax = 0;
				int areay = 0;
				int areawidth = 0;
				int areaheight = 0;
				if (scrollable) {
					// components are relative to the viewport
					Rectangle view = getRectangle(component, ":view");
					areawidth = view.width;
					areaheight = view.height;
				} else { // scrollpane isn't required
					// components are relative to top/left corner
					Rectangle bounds = getRectangle(component, "bounds");
					areawidth = bounds.width;
					areaheight = bounds.height;
					if ("panel" == classname) {
						areax = iborder;
						areay = Math.max(iborder, titleheight);
						areawidth -= 2 * iborder;
						areaheight -= areay + iborder;
					} else { // dialog
						areax = 4;
						areay = 4 + titleheight;
						areawidth -= 8;
						areaheight -= areay + 4;
					}
				}

				for (int i = 0; i < 2; i++) { // i=0: horizontal, i=1: vertical
					// remaining space
					int d = ((i == 0) ? (areawidth - contentwidth)
							: (areaheight - contentheight));
					if (d != 0) { //+ > 0
						int w = getSum(grid[2 + i], 0, grid[2 + i].length, 0,
								false);
						if (w > 0) {
							for (int j = 0; j < grid[i].length; j++) {
								if (grid[2 + i][j] != 0) {
									grid[i][j] += d * grid[2 + i][j] / w;
								}
							}
						}
					}
				}

				Object comp = get(component, ":comp");
				for (int i = 0; comp != null; comp = get(comp, ":next")) {
					if (!getBoolean(comp, "visible", true)) {
						continue;
					}
					int ix = areax + left
							+ getSum(grid[0], 0, grid[4][i], gap, true);
					int iy = areay + top
							+ getSum(grid[1], 0, grid[5][i], gap, true);
					int iwidth = getSum(grid[0], grid[4][i], grid[6][i], gap,
							false);
					int iheight = getSum(grid[1], grid[5][i], grid[7][i], gap,
							false);
					String halign = getString(comp, "halign", "fill");
					String valign = getString(comp, "valign", "fill");
					if ((halign != "fill") || (valign != "fill")) {
						Dimension d = getPreferredSize(comp);
						if (halign != "fill") {
							int dw = Math.max(0, iwidth - d.width);
							if (halign == "center") {
								ix += dw / 2;
							} else if (halign == "right") {
								ix += dw;
							}
							iwidth -= dw;
						}
						if (valign != "fill") {
							int dh = Math.max(0, iheight - d.height);
							if (valign == "center") {
								iy += dh / 2;
							} else if (valign == "bottom") {
								iy += dh;
							}
							iheight -= dh;
						}
					}
					setRectangle(comp, "bounds", ix, iy, iwidth, iheight);
					doLayout(comp);
					i++;
				}
			}
		} else if ("desktop" == classname) {
			Rectangle bounds = getRectangle(component, "bounds");
			for (Object comp = get(component, ":comp"); comp != null; comp = get(
					comp, ":next")) {
				String iclass = getClass(comp);
				if (iclass == "dialog") {
					Dimension d = getPreferredSize(comp);
					if (get(comp, "bounds") == null)
						setRectangle(comp, "bounds", Math.max(0,
								(bounds.width - d.width) / 2), Math.max(0,
								(bounds.height - d.height) / 2), Math.min(
								d.width, bounds.width), Math.min(d.height,
								bounds.height));
				} else if ((iclass != ":combolist") && (iclass != ":popup")) {
					setRectangle(comp, "bounds", 0, 0, bounds.width,
							bounds.height);
				}
				doLayout(comp);
			}
		} else if ("spinbox" == classname) {
			layoutField(component, block, false, 0);
		} else if ("splitpane" == classname) {
			Rectangle bounds = getRectangle(component, "bounds");
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int divider = getInteger(component, "divider", -1);
			int maxdiv = Math.max(0,
					(horizontal ? bounds.width : bounds.height) - 5);

			Object comp1 = get(component, ":comp");
			boolean visible1 = (comp1 != null)
					&& getBoolean(comp1, "visible", true);
			if (divider == -1) {
				int d1 = 0;
				if (visible1) {
					Dimension d = getPreferredSize(comp1);
					d1 = horizontal ? d.width : d.height;
				}
				divider = Math.min(d1, maxdiv);
				setInteger(component, "divider", divider, -1);
			} else if (divider > maxdiv) {
				setInteger(component, "divider", divider = maxdiv, -1);
			}

			if (visible1) {
				setRectangle(comp1, "bounds", 0, 0, horizontal ? divider
						: bounds.width, horizontal ? bounds.height : divider);
				doLayout(comp1);
			}
			Object comp2 = (comp1 != null) ? get(comp1, ":next") : null;
			if ((comp2 != null) && getBoolean(comp2, "visible", true)) {
				setRectangle(comp2, "bounds", horizontal ? (divider + 5) : 0,
						horizontal ? 0 : (divider + 5),
						horizontal ? (bounds.width - 5 - divider)
								: bounds.width, horizontal ? bounds.height
								: (bounds.height - 5 - divider));
				doLayout(comp2);
			}
		} else if (("list" == classname) || ("table" == classname)
				|| ("tree" == classname)) {
			int line = getBoolean(component, "line", true) ? 1 : 0;
			int width = 0;
			int columnheight = 0;
			if ("table" == classname) {
				Object header = get(component, "header");
				int[] columnwidths = null;
				if (header != null) {
					columnwidths = new int[getCount(header)];
					Object column = get(header, ":comp");
					for (int i = 0; i < columnwidths.length; i++) {
						if (i != 0) {
							column = get(column, ":next");
						}
						columnwidths[i] = getInteger(column, "width", 80);
						width += columnwidths[i];
						Dimension d = getSize(column, 2, 2);
						columnheight = Math.max(columnheight, d.height);
					}
				}
				set(component, ":widths", columnwidths);
			}
			int y = 0;
			int level = 0;
			for (Object item = get(component, ":comp"); item != null;) {
				int x = 0;
				int iwidth = 0;
				int iheight = 0;
				if ("table" == classname) {
					iwidth = width;
					for (Object cell = get(item, ":comp"); cell != null; cell = get(
							cell, ":next")) {
						Dimension d = getSize(cell, 2, 2);
						iheight = Math.max(iheight, d.height);
					}
				} else {
					if ("tree" == classname) {
						x = (level + 1) * block;
					}
					Dimension d = getSize(item, 6, 2);
					iwidth = d.width;
					iheight = d.height;
					width = Math.max(width, x + d.width);
				}
				setRectangle(item, "bounds", x, y, iwidth, iheight);
				y += iheight + line;
				if ("tree" == classname) {
					Object next = get(item, ":comp");
					if ((next != null) && getBoolean(item, "expanded", true)) {
						level++;
					} else {
						while (((next = get(item, ":next")) == null)
								&& (level > 0)) {
							item = getParent(item);
							level--;
						}
					}
					item = next;
				} else {
					item = get(item, ":next");
				}
			}
			layoutScroll(component, width, y - line, columnheight, 0, 0, 0,
					true, 0);
		} else if ("menubar" == classname) {

			Rectangle bounds = getRectangle(component, "bounds");
			int x = 0;
			for (Object menu = get(component, ":comp"); menu != null; menu = get(
					menu, ":next")) {
				Dimension d = getSize(menu, 8, 4);
				setRectangle(menu, "bounds", x, 0, d.width, bounds.height);
				x += d.width;
			}
		} else if ("bean" == classname) {
		}
	}

	/**
	 * The SAX-like parser calls this method, you have to overwrite it
	 */
	protected void endElement() {
	}

	/**
	 *  
	 */
	private void extend(Object component, Object lead, Object row,
			boolean recursive) {
		Object anchor = get(component, ":anchor");
		if (anchor == null) {
			set(component, ":anchor", anchor = lead);
		}
		char select = 'n';
		boolean changed = false;
		for (Object item = get(component, ":comp"); // anchor - row
		item != null; item = getNextItem(component, item, recursive)) {
			if (item == anchor)
				select = (select == 'n') ? 'y' : 'r';
			if (item == row)
				select = (select == 'n') ? 'y' : 'r';
			if (setBoolean(item, "selected", (select != 'n'), false)) {
				repaint(component, null, item);
				changed = true;
			}
			if (select == 'r')
				select = 'n';
		}
		if (changed) {
			invoke(component, row, "action");
		}
	}

	/**
	 * Fill the given rectangle with gradient
	 */
	private void fill(Graphics g, int x, int y, int width, int height,
			boolean horizontal) {

		horizontal = false;
		if (horizontal) {
			if (height > block) {
				g.setColor(c_bg);
				g.fillRect(x, y, width + evm, height - block + evm);
			}
			boolean adjust = true;
			for (int i = 0; i < width; i += block) {
				g.drawImage(hgradient, x + i,
						(height > block) ? (y + height - block) : y,
						Graphics.TOP | Graphics.LEFT);
				if (adjust && (i + block + block > width)) {
					i = width - block - block;
					adjust = false;
				}
			}
		} else {
			if (width > block) {
				g.setColor(c_bg);
				//                g.fillRect(x, y, width - block + evm, height + evm);
				g.fillRect(x, y, width, height);
			}
			boolean adjust = true;
			/*
			 * for (int i = 0; i < height; i += block) { g.drawImage(vgradient,
			 * (width > block) ? (x + width - block) : x, y + i, g.TOP |
			 * g.LEFT); if (adjust && (i + block + block> height)) { i = height -
			 * block - block; adjust = false; } }
			 */
		}
	}

	/**
	 * Finds the first component from the specified component by a name
	 * 
	 * @param component
	 *            the widget is searched inside this component
	 * @param name
	 *            parameter value identifies the widget
	 * @return the first suitable component, or null
	 */
	public Object find(Object component, String name) {
		if (name.equals(get(component, "name"))) {
			return component;
		}
		// otherwise search in its subcomponents
		Object found = null;
		for (Object comp = get(component, ":comp"); comp != null; comp = get(
				comp, ":next")) {
			if ((found = find(comp, name)) != null) {
				return found;
			}
		}
		// search in table header
		Object header = get(component, "header"); // if ("table" == classname)
		if ((header != null) && ((found = find(header, name)) != null)) {
			return found;
		}
		// search in component's popupmenu
		Object popupmenu = get(component, "popupmenu"); // if
		// instance(classname,
		// "component")
		if ((popupmenu != null) && ((found = find(popupmenu, name)) != null)) {
			return found;
		}
		return null;
	}

	/**
	 * Finds the first component from the root desktop by a specified name value
	 * 
	 * @param name
	 *            parameter value identifies the widget
	 * @return the first suitable component, or null
	 */
	public Object find(String name) {
		return find(content, name);
	}

	/**
	 *  
	 */
	private boolean findComponent(Object component, int x, int y) {
		if (component == content) {
			mouseinside = insidepart = null;
			mousex = x;
			mousey = y;
		}
		if (!getBoolean(component, "visible", true)) {
			return false;
		}
		Rectangle bounds = getRectangle(component, "bounds");
		if ((bounds == null) || !(bounds.contains(x, y))) {
			return false;
		}
		mouseinside = component;
		x -= bounds.x;
		y -= bounds.y;
		String classname = getClass(component);

		if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)
					&& (x <= bounds.width - block)) {
				Image icon = getIcon(component, "icon", null);
				insidepart = ((icon != null) && (x <= 2 + icon.getWidth())) ? "icon"
						: null;
			} else {
				insidepart = "down";
			}
		} else if ("bean" == classname) {
			set(component, ":mousex", new Integer(x));
			set(component, ":mousey", new Integer(y));
		} else if (":combolist" == classname) {
			if (!findScroll(component, x, y)) {
				y += getRectangle(component, ":view").y;
				for (Object choice = get(get(component, "combobox"), ":comp"); choice != null; choice = get(
						choice, ":next")) {
					Rectangle r = getRectangle(choice, "bounds");
					if ((y >= r.y) && (y < r.y + r.height)) {
						insidepart = choice;
						break;
					}
				}
			}
		} else if ("textarea" == classname) {
			findScroll(component, x, y);
		} else if ("tabbedpane" == classname) {
			int selected = getInteger(component, "selected", 0);
			int i = 0;
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				Rectangle r = getRectangle(tab, "bounds");
				if (i == selected) {
					Object tabcontent = get(tab, ":comp");
					if ((tabcontent != null)
							&& findComponent(tabcontent, x - r.x, y - r.y)) {
						break;
					}
				}
				if (r.contains(x, y)) {
					insidepart = tab;
					break;
				}
				i++;
			}
		} else if (("panel" == classname) || ("desktop" == classname)
				|| ("dialog" == classname)) {
			if ("dialog" == classname) {
				int titleheight = getInteger(component, ":titleheight", 0);
				boolean resizable = getBoolean(component, "resizable", false);
				if (resizable && (x < 4)) {
					insidepart = (y < block) ? ":nw" : (y >= bounds.height
							- block) ? ":sw" : ":w";
				} else if (resizable && (y < 4)) {
					insidepart = (x < block) ? ":nw" : (x >= bounds.width
							- block) ? ":ne" : ":n";
				} else if (resizable && (x >= bounds.width - 4)) {
					insidepart = (y < block) ? ":ne" : (y >= bounds.height
							- block) ? ":se" : ":e";
				} else if (resizable && (y >= bounds.height - 4)) {
					insidepart = (x < block) ? ":sw" : (x >= bounds.width
							- block) ? ":se" : ":s";
				} else {
					if (y < 4 + titleheight) {
						insidepart = "header";
					}
				}
				// close dialog button
				if (get(component, "close") != null) {
					int buttonX = bounds.width - titleheight - 1;
					int buttonY = 3;
					if (x > buttonX && x < buttonX + titleheight - 2
							&& y > buttonY && y < buttonY + titleheight - 2)
						insidepart = ":closebutton";
				}
			}
			if ((insidepart == null) && !findScroll(component, x, y)) {
				Rectangle port = getRectangle(component, ":port");
				if (port != null) { // content scrolled
					Rectangle view = getRectangle(component, ":view");
					x += view.x - port.x;
					y += view.y - port.y;
				}
				for (Object comp = get(component, ":comp"); comp != null; comp = get(
						comp, ":next")) {
					if (findComponent(comp, x, y)) {
						break;
					}
					if (("desktop" == classname)
							&& getBoolean(comp, "modal", false)) {
						insidepart = "modal";
						break;
					} // && dialog
				}
			}
		} else if ("spinbox" == classname) {
			insidepart = (x <= bounds.width - block) ? null
					: ((y <= bounds.height / 2) ? "up" : "down");
		} else if ("splitpane" == classname) {
			Object comp1 = get(component, ":comp");
			if (comp1 != null) {
				if (!findComponent(comp1, x, y)) {
					Object comp2 = get(comp1, ":next");
					if (comp2 != null) {
						findComponent(comp2, x, y);
					}
				}
			}
		} else if ("list" == classname) {
			findScroll(component, x, y);
		} else if ("table" == classname) {
			// check if we are inside the header, then if we have an action on
			// this
			// header or it is a
			// resizable one, do further checks to see if we need to change the
			// column
			// drawing states or
			// even need to invoke the action or to resize the column. We do not
			// need
			// to make this whole
			// block if we are resizing a column right now
			Object header = get(component, "header");
			if (header != null && get(header, ":resizing") == null) {
				boolean isResizable = getBoolean(header, "resizable");
				boolean hasAction = null != get(header, "action");
				if (isResizable || hasAction) {
					Rectangle view = getRectangle(component, ":view");
					Rectangle port = getRectangle(component, ":port");
					if (0 < x && x < port.width && 0 < y && y < port.y - 1) {
						int[] columnwidths = (int[]) get(component, ":widths");
						Object column = get(header, ":comp");
						int left = -view.x;
						for (int i = 0; i < columnwidths.length; i++) {
							if (i != 0)
								column = get(column, ":next");
							int width = (i == columnwidths.length - 1) ? (view.width
									- left + 2)
									: columnwidths[i];
							if (isResizable
									&& ((x > left + width - 4 && x < left
											+ width) || (i < columnwidths.length - 1
											&& x >= left + width && x < left
											+ width + 4))) {
								set(header, ":resizecomponent", column);
								break;
							} else {
								set(header, ":resizecomponent", null);
								if (hasAction && getCount(component) > 0
										&& left < x && x < left + width) {
									insidepart = column;
									break;
								}
							}
							left += width;
						}
					} else if (isResizable) {
						set(header, ":resizecomponent", null);
						set(header, ":resizing", null);
					}
				}
			}
			if (insidepart == null && get(header, ":resizecomponent") == null)
				findScroll(component, x, y);
		} else if ("tree" == classname) {
			findScroll(component, x, y);
		} else if ("menubar" == classname) {
			for (Object menu = get(component, ":comp"); menu != null; menu = get(
					menu, ":next")) {
				Rectangle r = getRectangle(menu, "bounds");
				if ((x >= r.x) && (x < r.x + r.width)) {
					insidepart = menu;
					break;
				}
			}
		} else if (":popup" == classname) {
			for (Object menu = get(get(component, "menu"), ":comp"); menu != null; menu = get(
					menu, ":next")) {
				Rectangle r = getRectangle(menu, "bounds");
				if ((y >= r.y) && (y < r.y + r.height)) {
					insidepart = menu;
					break;
				}
			}
		}
		return true;
	}

	/**
	 * @return the first or the next item of the (list, table, or tree)
	 *         component
	 */
	private Object findNextItem(Object component, String classname, Object item) {
		if (item == null) { // first item
			return get(component, ":comp");
		} else if ("tree" == classname) { // next tree node
			Object next = get(item, ":comp");
			if ((next == null) || !getBoolean(item, "expanded", true)) { // no
				// subnode
				// or
				// collapsed
				while ((item != component)
						&& ((next = get(item, ":next")) == null)) {
					item = getParent(item); //next node of in backward path
				}
			}
			return next;
		} else { //next list or tree item
			return get(item, ":next");
		}
	}

	/**
	 * @param p
	 *            x or y relative to the scrollbar begin
	 * @param size
	 *            scrollbar width or height
	 * @param portsize
	 *            viewport width or height
	 * @param viewp
	 *            view x or y
	 * @param viewsize
	 *            view width or height
	 * @param horizontal
	 *            if true horizontal, vertical otherwise
	 */
	private void findScroll(int p, int size, int portsize, int viewp,
			int viewsize, boolean horizontal) {
		if (p < block) {
			insidepart = horizontal ? "left" : "up";
		} else if (p > size - block) {
			insidepart = horizontal ? "right" : "down";
		} else {
			int track = size - 2 * block;
			if (track < 10) {
				insidepart = "corner";
				return;
			} // too small
			int knob = Math.max(track * portsize / viewsize, 10);
			int decrease = viewp * (track - knob) / (viewsize - portsize);
			if (p < block + decrease) {
				insidepart = horizontal ? "lefttrack" : "uptrack";
			} else if (p < block + decrease + knob) {
				insidepart = horizontal ? "hknob" : "vknob";
			} else {
				insidepart = horizontal ? "righttrack" : "downtrack";
			}
		}
	}

	/**
	 * @param component
	 *            a scrollable widget
	 * @param x
	 *            point x location
	 * @param y
	 *            point y location
	 * @return true if the point (x, y) is inside scroll-control area
	 *         (scrollbars, corners, borders), false otherwise (vievport,
	 *         header, or no scrollpane)
	 */
	private boolean findScroll(Object component, int x, int y) {
		Rectangle port = getRectangle(component, ":port");
		if ((port == null) || port.contains(x, y)) {
			return false;
		}
		Rectangle view = getRectangle(component, ":view");
		Rectangle horizontal = getRectangle(component, ":horizontal");
		Rectangle vertical = getRectangle(component, ":vertical");
		if ((horizontal != null) && horizontal.contains(x, y)) {
			findScroll(x - horizontal.x, horizontal.width, port.width, view.x,
					view.width, true);
		} else if ((vertical != null) && vertical.contains(x, y)) {
			findScroll(y - vertical.y, vertical.height, port.height, view.y,
					view.height, false);
		} else {
			insidepart = "corner";
		}
		return true;
	}

	/**
	 * Search for the next/first appropriate item starting with the collected
	 * string or the given single character
	 * 
	 * @param keychar
	 *            the last typed character
	 * @param component
	 *            a list, tree, table, or combobox
	 * @param leadowner
	 *            the list, tree, table, or the combobox's drop down list
	 * @param recursive
	 *            if the component is a tree
	 * @return the appropriate item or null
	 */
	private Object findText(char keychar, Object component, Object leadowner,
			boolean recursive) {
		if (keychar != 0) {
			long current = System.currentTimeMillis();
			int i = (current > findtime + 1000) ? 1 : 0; // clear the starting
			// string
			// after a second
			findtime = current;
			Object lead = get(leadowner, ":lead");
			for (; i < 2; i++) { // 0: find the long text, 1: the stating
				// character
				// only
				findprefix = (i == 0) ? (findprefix + keychar) : String
						.valueOf(keychar);
				for (int j = 0; j < 2; j++) { // 0: lead to last, 1: first to
					// lead
					for (Object item = (j == 0) ? ((i == 0) ? lead
							: getNextItem(component, lead, recursive)) : get(
							component, ":comp"); (j == 0) ? (item != null)
							: (item != lead); item = getNextItem(component,
							item, recursive)) {
						if (getString(item, "text", "").regionMatches(true, 0,
								findprefix, 0, findprefix.length())) { //table
							// first
							// column...
							return item;
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 *  
	 */
	private void finishParse(Vector methods, Object root, Object handler) {
		if (methods != null) {
			for (int i = 0; i < methods.size(); i += 3) {
				Object component = methods.elementAt(i);
				Object[] definition = (Object[]) methods.elementAt(i + 1);
				String value = (String) methods.elementAt(i + 2);

				if ("method" == definition[0]) {
					Object[] method = getMethod(component, value, root, handler);
					if ("init" == definition[1]) {
						invoke(method, null, null);
					} else {
						set(component, definition[1], method);
					}
				} else { // ("component" == definition[0])
					Object reference = find(root, value); //+start find from
					// the
					// component
					if (reference == null)
						throw new IllegalArgumentException(value + " not found");
					set(component, definition[1], reference);
				}
			}
		}
	}

	/**
	 * @param component
	 *            a menuitem
	 * @return key modifier strings and key text
	 */
	private String getAccelerator(Object component) {
		Object accelerator = get(component, "accelerator");
		if (accelerator != null) {
			long keystroke = ((Long) accelerator).longValue();
			int keycode = (int) (keystroke >> 32);
			int modifiers = (int) (keystroke & 0xffff);
			return "" + (char) keycode;
		}
		return null;
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public boolean getBoolean(Object component, String key) {
		return get(component, key, "boolean") == TRUE;
	}

	/**
	 *  
	 */
	private boolean getBoolean(Object component, String key,
			boolean defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : ((Boolean) value)
				.booleanValue();
	}

	/**
	 *  
	 */
	private int getCaretLocation(Object component, int x, int y,
			boolean multiline, boolean hidden) {
		//SH
		//Font currentfont = (Font) get(component, "font");
		Font currentfont = font;
		if (currentfont == null)
			currentfont = font;
		char[] chars = multiline ? ((char[]) get(component, ":text"))
				: getString(component, "text", "").toCharArray(); // update
		// it
		int linestart = 0;
		if (multiline) {
			int height = currentfont.getHeight(); // find the line start by y
												  // value
			for (int i = 0; (y >= height) && (i < chars.length); i++) {
				if ((chars[i] == '\n') || (chars[i] == '\t')) {
					linestart = i + 1;
					y -= height;
				}
			}
		}

		//
		// Adjust for non-left alignment of text fields
		//
		String classname = getClass(component);
		if ("textfield" == classname) {
			String alignment = getChoice(component, "alignment");
			String t = getString(component, "text");
			int tw = currentfont.stringWidth(t);
			int w = getRectangle(component, "bounds").width;
			if ("center" == alignment) {
				if (tw < w)
					x -= (w - tw - 2) / 2;
			} else if ("right" == alignment) {
				if (tw < w)
					x -= (w - tw - 2);
			}
		}

		for (int i = linestart; i < chars.length; i++) {
			if ((chars[i] == '\n') || (chars[i] == '\t')) {
				return i;
			}
			int charwidth = currentfont.charWidth(hidden ? '*' : chars[i]);
			if (x <= (charwidth / 2)) {
				return i;
			}
			x -= charwidth;
		}
		return chars.length;
	}

	/**
	 *  
	 */
	private char[] getChars(Object component, String text, boolean wrap,
			int width, int height) {
		char[] chars = (char[]) get(component, ":text");
		if ((chars == null) || (chars.length != text.length())) {
			chars = text.toCharArray();
			set(component, ":text", chars);
		} else
			text.getChars(0, chars.length, chars, 0);

		if (wrap) {
			//SH
			//Font currentfont = (Font) get(component, "font");
			Font currentfont = font;
			if (currentfont == null)
				currentfont = font;
			int lines = (height - 4) / currentfont.getHeight();
			boolean prevletter = false;
			int n = chars.length;
			int linecount = 0;
			for (int i = 0, j = -1, k = 0; k <= n; k++) { // j is the last space
				// index
				// (before k)
				if (((k == n) || (chars[k] == '\n') || (chars[k] == ' '))
						&& (j > i)
						&& (currentfont.charsWidth(chars, i, k - i) > width)) {
					chars[j] = '\n';
					k--; // draw line to the begin of the current word (+
					// spaces) if it is
					// out of width
				} else if ((k == n) || (chars[k] == '\n')) { // draw line to the
					// text/line end
					j = k;
					prevletter = false;
				} else {
					if ((chars[k] == ' ') && (prevletter || (j > i))) {
						j = k;
					} // keep spaces starting the line
					prevletter = (chars[k] != ' ');
					continue;
				}
				linecount++;
				if ((lines != 0) && (linecount == lines)) {
					return null;
				}
				i = j + 1;
			}
		}
		return chars;
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public String getChoice(Object component, String key) {
		Object[] definition = getDefinition(getClass(component), key, "choice");
		return getString(component, (String) definition[1],
				((String[]) definition[3])[0]);
	}

	/**
	 * Gets the count of sub-components in the list of the given component
	 * 
	 * @param component
	 *            a widget
	 * @return the number of components in this component
	 */
	public int getCount(Object component) {
		return getItemCountImpl(component, ":comp");
	}

	/**
	 * Get the topmost component
	 * 
	 * @return the root object (it is a <i>desktop </i>), never <i>null </i>
	 */
	public Object getDesktop() {//#
		return content;
	}

	/**
	 *  
	 */
	private Dimension getFieldSize(Object component) {
		int columns = getInteger(component, "columns", 0);
		//SH
		//Font currentfont = (Font) get(component, "font");
		Font currentfont = font;
		if (currentfont == null)
			currentfont = font;
		return new Dimension(((columns > 0) ? (columns * currentfont
				.charWidth('e')) : 76) + 4, currentfont.getHeight() + 4); // fm.stringWidth(text)
	}

	private int getFontFace(String name) {
		if (name == null)
			return Font.FACE_SYSTEM;
		name = name.toLowerCase();
		if (name.equals("system"))
			return Font.FACE_SYSTEM;
		if (name.equals("courier") || name.equals("terminal")
				|| name.equals("monospace"))
			return Font.FACE_MONOSPACE;
		return Font.FACE_PROPORTIONAL;
	}

	/**
	 * @param component
	 *            a container
	 * @return null for zero visible subcomponent, otherwise an array contains
	 *         the following lists:
	 *         <ul>
	 *         <li>columnwidths, preferred width of grid columns</li>
	 *         <li>rowheights, preferred heights of grid rows</li>
	 *         <li>columnweights, grid column-width weights</li>
	 *         <li>rowweights, grid row-height weights</li>
	 *         <li>gridx, horizontal location of the subcomponents</li>
	 *         <li>gridy, vertical locations</li>
	 *         <li>gridwidth, column spans</li>
	 *         <li>gridheight, row spans</li>
	 *         </ul>
	 */
	private int[][] getGrid(Object component) {
		int count = 0; // count of the visible subcomponents
		for (Object comp = get(component, ":comp"); comp != null; comp = get(
				comp, ":next")) {
			if (getBoolean(comp, "visible", true)) {
				count++;
			}
		}
		if (count == 0) {
			return null;
		} // zero subcomponent
		int columns = getInteger(component, "columns", 0);
		int icols = (columns != 0) ? columns : count;
		int irows = (columns != 0) ? ((count + columns - 1) / columns) : 1;
		int[][] grid = { new int[icols], new int[irows], // columnwidths,
				// rowheights
				new int[icols], new int[irows], // columnweights, rowweights
				new int[count], new int[count], // gridx, gridy
				new int[count], new int[count] }; // gridwidth, gridheight
		int[] columnheight = new int[icols];
		int[][] cache = null; // preferredwidth, height, columnweight, rowweight

		int i = 0;
		int x = 0;
		int y = 0;
		int nextsize = 0;
		for (Object comp = get(component, ":comp"); comp != null; comp = get(
				comp, ":next")) {
			if (!getBoolean(comp, "visible", true)) {
				continue;
			}
			int colspan = ((columns != 0) && (columns < count)) ? Math.min(
					getInteger(comp, "colspan", 1), columns) : 1;
			int rowspan = (columns != 1) ? getInteger(comp, "rowspan", 1) : 1;

			for (int j = 0; j < colspan; j++) {
				if ((columns != 0) && (x + colspan > columns)) {
					x = 0;
					y++;
					j = -1;
				} else if (columnheight[x + j] > y) {
					x += (j + 1);
					j = -1;
				}
			}
			if (y + rowspan > grid[1].length) {
				int[] rowheights = new int[y + rowspan];
				System.arraycopy(grid[1], 0, rowheights, 0, grid[1].length);
				grid[1] = rowheights;
				int[] rowweights = new int[y + rowspan];
				System.arraycopy(grid[3], 0, rowweights, 0, grid[3].length);
				grid[3] = rowweights;
			}
			for (int j = 0; j < colspan; j++) {
				columnheight[x + j] = y + rowspan;
			}

			int weightx = getInteger(comp, "weightx", 0);
			int weighty = getInteger(comp, "weighty", 0);
			Dimension d = getPreferredSize(comp);

			if (colspan == 1) {
				grid[0][x] = Math.max(grid[0][x], d.width); // columnwidths
				grid[2][x] = Math.max(grid[2][x], weightx); // columnweights
			} else {
				if (cache == null) {
					cache = new int[4][count];
				}
				cache[0][i] = d.width;
				cache[2][i] = weightx;
				if ((nextsize == 0) || (colspan < nextsize)) {
					nextsize = colspan;
				}
			}
			if (rowspan == 1) {
				grid[1][y] = Math.max(grid[1][y], d.height); // rowheights
				grid[3][y] = Math.max(grid[3][y], weighty); // rowweights
			} else {
				if (cache == null) {
					cache = new int[4][count];
				}
				cache[1][i] = d.height;
				cache[3][i] = weighty;
				if ((nextsize == 0) || (rowspan < nextsize)) {
					nextsize = rowspan;
				}
			}
			grid[4][i] = x; //gridx
			grid[5][i] = y; //gridy
			grid[6][i] = colspan; //gridwidth
			grid[7][i] = rowspan; //gridheight

			x += colspan;
			i++;
		}

		while (nextsize != 0) {
			int size = nextsize;
			nextsize = 0;
			for (int j = 0; j < 2; j++) { // horizontal, vertical
				for (int k = 0; k < count; k++) {
					if (grid[6 + j][k] == size) { // gridwidth, gridheight
						int gridpoint = grid[4 + j][k]; // gridx, gridy

						int weightdiff = cache[2 + j][k];
						for (int m = 0; (weightdiff > 0) && (m < size); m++) {
							weightdiff -= grid[2 + j][gridpoint + m];
						}
						if (weightdiff > 0) {
							int weightsum = cache[2 + j][k] - weightdiff;
							for (int m = 0; (weightsum > 0) && (m < size); m++) {
								int weight = grid[2 + j][gridpoint + m];
								if (weight > 0) {
									int weightinc = weight * weightdiff
											/ weightsum;
									grid[2 + j][gridpoint + m] += weightinc;
									weightdiff -= weightinc;
									weightsum -= weightinc;
								}
							}
							grid[2 + j][gridpoint + size - 1] += weightdiff;
						}

						int sizediff = cache[j][k];
						int weightsum = 0;
						for (int m = 0; (sizediff > 0) && (m < size); m++) {
							sizediff -= grid[j][gridpoint + m];
							weightsum += grid[2 + j][gridpoint + m];
						}
						if (sizediff > 0) {
							for (int m = 0; (weightsum > 0) && (m < size); m++) {
								int weight = grid[2 + j][gridpoint + m];
								if (weight > 0) {
									int sizeinc = weight * sizediff / weightsum;
									grid[j][gridpoint + m] += sizeinc;
									sizediff -= sizeinc;
									weightsum -= weight;
								}
							}
							grid[j][gridpoint + size - 1] += sizediff;
						}
					} else if ((grid[6 + j][k] > size)
							&& ((nextsize == 0) || (grid[6 + j][k] < nextsize))) {
						nextsize = grid[6 + j][k];
					}
				}
			}
		}
		return grid;
	}

	protected int getHeight(Object component) {
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds == null) {
			Dimension dim = getPreferredSize(component);
			bounds = new Rectangle(0, 0, dim.width, dim.height);
			set(component, "bounds", bounds);
		}
		return bounds.height;
	}

	/**
	 *  
	 */
	private String getI18NString(Object component, String key, String text) { // for
		// I18N
		if (allI18n && (langResource != null)
				&& ((key == "text") || (key == "tooltip"))
				&& getBoolean(component, "i18n", true)) {
			String ikey = (String) getProperty(component, "i18n." + key);
			if (!"__NONE__".equals(ikey)) {
				if (ikey == null) { // initialize
					putProperty(component, "i18n." + key, ikey = text);
				}
				try {
					return (String) langResource.get(ikey);
				} catch (Exception exc) { // not found. Try default
					if (langResourceDefault != null) {
						try {
							return (String) langResourceDefault.get(ikey);
						} catch (Exception dexc) {
							putProperty(component, "i18n." + key, "__NONE__");
						}
					}
				}
			}
		}
		return text;
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public Image getIcon(Object component, String key) {
		return (Image) get(component, key, "icon");
	}

	/**
	 *  
	 */
	private Image getIcon(Object component, String key, Image defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : (Image) value;
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Creates an image, and loads it immediately by default
	 * 
	 * @param path
	 *            is relative to your thinlet instance or the classpath (if the
	 *            path starts with <i>'/' </i> character), or a full URL
	 * @return the loaded image or null
	 */
	public Image getIcon(String path) {
		return getIcon(path, true);
	}

	/**
	 * Creates an image from the specified resource. To speed up loading the
	 * same images use a cache (a simple hashtable). And flush the resources
	 * being used by an image when you won't use it henceforward
	 * 
	 * @param path
	 *            is relative to your thinlet instance or the classpath, or an
	 *            URL
	 * @param preload
	 *            waits for the whole image if true, starts loading (and
	 *            repaints, and updates the layout) only when required (painted,
	 *            or size requested) if false
	 * @return the loaded image or null
	 */
	public Image getIcon(String path, boolean preload) {
		if ((path == null) || (path.length() == 0)) {
			return null;
		}
		Image image = null; //(Image) imagepool.get(path);
		/*
		 * try { URL url = getClass().getResource(path);
		 * //ClassLoader.getSystemResource(path) if (url != null) { //
		 * contributed by Stefan Matthias Aust image =
		 * Toolkit.getDefaultToolkit().getImage(url); } } catch (Throwable e) { }
		 */
		try {
			image = Image.createImage(path);
		} catch (IOException ioe) {
		}
		if (image == null) {
			try {
				InputStream is = getClass().getResourceAsStream(path);
				//InputStream is = ClassLoader.getSystemResourceAsStream(path);
				if (is != null) {
					byte[] data = new byte[is.available()];
					is.read(data, 0, data.length);
					image = Image.createImage(data, 0, data.length);
					is.close();
				}
			} catch (Throwable e) {
			}
		}
		/*
		 * if (preload && (image != null)) { MediaTracker mediatracker = new
		 * MediaTracker(this); mediatracker.addImage(image, 1); try {
		 * mediatracker.waitForID(1, 5000); } catch (InterruptedException ie) { }
		 * //imagepool.put(path, image); }
		 */
		return image;
	}

	/**
	 *  
	 */
	private int getIndex(Object component, Object value) {
		int index = 0;
		for (Object item = get(component, ":comp"); item != null; item = get(
				item, ":next")) {
			if (value == item) {
				return index;
			}
			index++;
		}
		return -1;
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public int getInteger(Object component, String key) {
		return ((Integer) get(component, key, "integer")).intValue();
	}

	/**
	 *  
	 */
	private int getInteger(Object component, String key, int defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : ((Integer) value).intValue();
	}

	/**
	 * Returns the subcomponent of the given component's specified list at the
	 * given index
	 * 
	 * @param component
	 *            a specified container
	 * @param index
	 *            the index of the component to get
	 * @return the index <sup>th </sup> component in this container
	 */
	public Object getItem(Object component, int index) {
		return getItemImpl(component, ":comp", index);
	}

	/**
	 * Gets all the components in this container
	 * 
	 * @param component
	 *            a specified container
	 * @return an array of all the components in this container
	 */
	public Object[] getItems(Object component) {
		Object[] items = new Object[getItemCountImpl(component, ":comp")];
		Object comp = get(component, ":comp");
		for (int i = 0; i < items.length; i++) {
			items[i] = comp;
			comp = get(comp, ":next");
		}
		return items;
	}

	/**
	 *  
	 */
	private Object getListItem(Object component, Object scrollpane,
			int keycode, Object lead, boolean recursive) {
		Object row = null;
		if (keycode == KeyEvent.VK_UP) {
			for (Object prev = get(component, ":comp"); prev != lead; prev = getNextItem(
					component, prev, recursive)) {
				row = prev; // component -> getParent(lead)
			}
		} else if (keycode == KeyEvent.VK_DOWN) {
			row = (lead == null) ? get(component, ":comp") : getNextItem(
					component, lead, recursive);
		} else if ((keycode == KeyEvent.VK_PAGE_UP)
				|| (keycode == KeyEvent.VK_PAGE_DOWN)) {
			Rectangle view = getRectangle(scrollpane, ":view");
			Rectangle port = getRectangle(scrollpane, ":port");
			Rectangle rl = (lead != null) ? getRectangle(lead, "bounds") : null;
			int vy = (keycode == KeyEvent.VK_PAGE_UP) ? view.y
					: (view.y + port.height);
			if ((keycode == KeyEvent.VK_PAGE_UP) && (rl != null)
					&& (rl.y <= view.y)) {
				vy -= port.height;
			}
			if ((keycode == KeyEvent.VK_PAGE_DOWN) && (rl != null)
					&& (rl.y + rl.height >= view.y + port.height)) {
				vy += port.height;
			}
			for (Object item = get(component, ":comp"); item != null; item = getNextItem(
					component, item, recursive)) {
				Rectangle r = getRectangle(item, "bounds");
				if (keycode == KeyEvent.VK_PAGE_UP) {
					row = item;
					if (r.y + r.height > vy) {
						break;
					}
				} else {
					if (r.y > vy) {
						break;
					}
					row = item;
				}
			}
		} else if (keycode == KeyEvent.VK_HOME) {
			row = get(component, ":comp");
		} else if (keycode == KeyEvent.VK_END) {
			for (Object last = lead; last != null; last = getNextItem(
					component, last, recursive)) {
				row = last;
			}
		}
		return row;
	}

	/**
	 * @param component
	 *            a :popup or a menubar
	 * @param part
	 *            the currently selected item, return the first/last if null
	 * @param forward
	 *            find the next item if true, the previous otherwise
	 * @param popup
	 *            the given component is :popup if true, menubar otherwise
	 * @return the next/previous item relative to the current one excluding
	 *         separators, or null
	 */
	private Object getMenu(Object component, Object part, boolean forward,
			boolean popup) {
		Object previous = null;
		for (int i = 0; i < 2; i++) { // 0: next to last, 1: first to previous
			for (Object item = (i == 0) ? get(part, ":next") : get(popup ? get(
					component, "menu") : component, ":comp"); (i == 0) ? (item != null)
					: (item != part); item = get(item, ":next")) {
				if ((getClass(item) != "separator")
						&& getBoolean(item, "enabled", true)) {
					if (forward) {
						return item;
					}
					previous = item;
				}
			}
		}
		return previous;
	}

	/**
	 * @return an object list including as follows: - handler object, - method, -
	 *         list of parameters including 3 values: - ("thinlet", null, null)
	 *         for the single thinlet component, - (target component, null,
	 *         null) for named widget as parameter, e.g. mybutton, - (target,
	 *         parameter name, default value) for a widget's given property,
	 *         e.g. mylabel.enabled, - ("item", null, null) for an item of the
	 *         target component as parameter, e.g. tree node, - ("item",
	 *         parameter name, default value) for the item's given property e.g.
	 *         list item's text.
	 */
	private Object[] getMethod(Object component, String value, Object root,
			Object handler) {
		StringTokenizer st = new StringTokenizer(value, "(, \r\n\t)");
		String methodname = st.nextToken();
		int n = st.countTokens();
		Object[] data = new Object[2 + 3 * n];
		String[] parametertypes = (n > 0) ? new String[n] : null;
		for (int i = 0; i < n; i++) {
			String arg = st.nextToken();
			if ("thinlet".equals(arg)) {
				data[2 + 3 * i] = "thinlet"; // the target component
				parametertypes[i] = "Thinlet";
			} else {
				int dot = arg.indexOf('.');
				String compname = (dot == -1) ? arg : arg.substring(0, dot);
				Object comp = null;
				String classname = null;
				if ("item".equals(compname)) {
					comp = "item";
					String parentclass = getClass(component);
					if ("list" == parentclass) {
						classname = "item";
					} else if ("tree" == parentclass) {
						classname = "node";
					} else if ("table" == parentclass) {
						classname = "row";
					} else if ("combobox" == parentclass) {
						classname = "choice";
					} else if ("tabbedpane" == parentclass) {
						classname = "tab";
					} else
						throw new IllegalArgumentException(parentclass
								+ " has no item");
				} else {
					comp = ("this".equals(compname)) ? component : find(root,
							compname);
					classname = getClass(comp);
				}
				data[2 + 3 * i] = comp; // the target component
				if (dot == -1) {
					parametertypes[i] = "Object"; // Widget.class
				} else {
					Object[] definition = getDefinition(classname, arg
							.substring(dot + 1), null);
					data[2 + 3 * i + 1] = definition[1]; // parameter name, e.g.
					// enabled
					data[2 + 3 * i + 2] = definition[3]; // default value, e.g.
					// TRUE
					Object fieldclass = definition[0];
					if ((fieldclass == "string") || (fieldclass == "choice")) {
						parametertypes[i] = "String";
					} else if (fieldclass == "boolean") {
						parametertypes[i] = "Boolean";
					} else if (fieldclass == "integer") {
						parametertypes[i] = "Integer";
					} else if (fieldclass == "icon") {
						parametertypes[i] = "Image";
					} else
						throw new IllegalArgumentException((String) fieldclass);
				}
			}
		}
		data[0] = handler;
		try {
			//data[1] = handler.getClass().getMethod(methodname,
			// parametertypes);
			data[1] = methodname;
			return data;
		} catch (Exception exc) {
			throw new IllegalArgumentException(value + " " + exc.getMessage());
		}
	}

	/**
	 * Find the next item after the given
	 * 
	 * @param component
	 *            a list/tree/table widget
	 * @param item
	 *            the next item after this, or the first if null
	 * @param recursive
	 *            true if tree
	 * @return next (or first) item
	 */
	private Object getNextItem(Object component, Object item, boolean recursive) {
		if (!recursive) {
			return get(item, ":next");
		}
		Object next = get(item, ":comp");
		if ((next == null) || !getBoolean(item, "expanded", true)) {
			while ((item != component) && ((next = get(item, ":next")) == null)) {
				item = getParent(item);
			}
		}
		return next;
	}

	/**
	 * Gets the parent of this component
	 * 
	 * @param component
	 *            a widget
	 * @return the parent container of this component or item
	 */
	public Object getParent(Object component) {
		return get(component, ":parent");
	}

	/**
	 * Gets the preferred size of the root component
	 * 
	 * @return a dimension object indicating the root component's preferred size
	 */
	public Dimension getPreferredSize() {
		return getPreferredSize(content);
	}

	/**
	 * 
	 * @throws java.lang.IllegalArgumentException
	 */
	private Dimension getPreferredSize(Object component) {
		int width = getInteger(component, "width", 0);
		int height = getInteger(component, "height", 0);
		if ((width > 0) && (height > 0)) {
			return new Dimension(width, height);
		}
		String classname = getClass(component);
		if ("label" == classname) {
			return getSize(component, 0, 0);
		}
		if (("button" == classname) || ("togglebutton" == classname)) {
			boolean link = ("button" == classname)
					&& (get(component, "type") == "link");
			return getSize(component, link ? 0 : 12, link ? 0 : 6);
		}
		if ("checkbox" == classname) {
			Dimension d = getSize(component, 0, 0);
			d.width = d.width + block + 3;
			d.height = Math.max(block, d.height);
			return d;
		}
		if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)) {
				Dimension size = getFieldSize(component);
				Image icon = getIcon(component, "icon", null);
				if (icon != null) {
					size.width += icon.getWidth();
					size.height = Math.max(size.height, icon.getHeight() + 2);
				}
				size.width += block;
				return size;
			} else {
				// maximum size of current values and choices including 2-2-2-2
				// insets
				Dimension size = getSize(component, 4, 4);
				for (Object item = get(component, ":comp"); item != null; item = get(
						item, ":next")) {
					Dimension d = getSize(item, 4, 4);
					size.width = Math.max(d.width, size.width);
					size.height = Math.max(d.height, size.height);
				}
				size.width += block;
				if (size.height == 4) { // no content nor items, set text height
					//SH
					//Font customfont = (Font) get(component, "font");
					Font customfont = font;
					if (customfont == null)
						customfont = font;
					size.height = customfont.getHeight() + 4;
				}
				return size;
			}
		}
		if (("textfield" == classname) || ("passwordfield" == classname)) {
			return getFieldSize(component);
		}
		if ("textarea" == classname) {
			int columns = getInteger(component, "columns", 0);
			int rows = getInteger(component, "rows", 0); // 'e' -> 'm' ?
			//SH
			//Font currentfont = (Font) get(component, "font");
			Font currentfont = font;
			if (currentfont == null)
				currentfont = font;
			return new Dimension(((columns > 0) ? (columns
					* currentfont.charWidth('e') + 2) : 76)
					+ 2 + block,
					((rows > 0) ? (rows * currentfont.getHeight() + 2) : 76)
							+ 2 + block);
		}
		if ("tabbedpane" == classname) {
			String placement = getString(component, "placement", "top");
			boolean horizontal = ((placement != "left") && (placement != "right"));
			int tabsize = 0; // max tab height (for horizontal),
			// max tabwidth (for vertical), or sum of tab heights for stacked
			int contentwidth = 0;
			int contentheight = 0; // max content size
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				Dimension d = getSize(tab, 0, 0);
				if (placement == "stacked") {
					tabsize += d.height + 3;
				} else {
					tabsize = Math.max(tabsize, horizontal ? d.height + 5
							: d.width + 9);
				}

				Object comp = get(tab, ":comp");
				if ((comp != null) && getBoolean(comp, "visible", true)) {
					Dimension dc = getPreferredSize(comp);
					contentwidth = Math.max(contentwidth, dc.width);
					contentheight = Math.max(contentheight, dc.height);
				}
			}
			return new Dimension(contentwidth
					+ (horizontal ? 4 : (tabsize + 3)), contentheight
					+ (horizontal ? (tabsize + 3) : 4));
		}
		if (("panel" == classname) || (classname == "dialog")) {
			// title text and icon height
			Dimension size = getSize(component, 0, 0);
			// add border size
			if (classname == "dialog") {
				size.width = 8;
				size.height += 8; // title width neglected
			} else if (getBoolean(component, "border", false)) { // bordered
				// panel
				size.width = 2;
				size.height += (size.height > 0) ? 1 : 2; // title includes line
			} else {
				size.width = 0;
			} // title width is clipped
			// add paddings
			size.width += getInteger(component, "left", 0)
					+ getInteger(component, "right", 0);
			size.height += getInteger(component, "top", 0)
					+ getInteger(component, "bottom", 0);
			// add content preferred size
			int gap = getInteger(component, "gap", 0);
			int[][] grid = getGrid(component);
			if (grid != null) { // has components
				size.width += getSum(grid[0], 0, grid[0].length, gap, false);
				size.height += getSum(grid[1], 0, grid[1].length, gap, false);
			}
			return size;
		} else if ("desktop" == classname) {
			Dimension size = new Dimension();
			for (Object comp = get(component, ":comp"); comp != null; comp = get(
					comp, ":next")) {
				String iclass = getClass(comp);
				if ((iclass != "dialog") && (iclass != ":popup")
						&& (iclass != ":combolist")) {
					Dimension d = getPreferredSize(comp);
					size.width = Math.max(d.width, size.width);
					size.height = Math.max(d.height, size.height);
				}
			}
			return size;
		}
		if ("spinbox" == classname) {
			Dimension size = getFieldSize(component);
			size.width += block;
			return size;
		}
		if ("progressbar" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			return new Dimension(horizontal ? 76 : 6, horizontal ? 6 : 76);
		}
		if ("slider" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			return new Dimension(horizontal ? 76 : 10, horizontal ? 10 : 76);
		}
		if ("splitpane" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			Object comp1 = get(component, ":comp");
			Dimension size = ((comp1 == null) || !getBoolean(comp1, "visible",
					true)) ? new Dimension() : getPreferredSize(comp1);
			Object comp2 = get(comp1, ":next");
			if ((comp2 != null) && getBoolean(comp2, "visible", true)) {
				Dimension d = getPreferredSize(comp2);
				size.width = horizontal ? (size.width + d.width) : Math.max(
						size.width, d.width);
				size.height = horizontal ? Math.max(size.height, d.height)
						: (size.height + d.height);
			}
			if (horizontal) {
				size.width += 5;
			} else {
				size.height += 5;
			}
			return size;
		}
		if (("list" == classname) || ("table" == classname)
				|| ("tree" == classname)) {
			return new Dimension(76 + 2 + block, 76 + 2 + block);
		}
		if ("separator" == classname) {
			return new Dimension(1, 1);
		}
		if ("menubar" == classname) {
			Dimension size = new Dimension(0, 0);
			for (Object menu = get(component, ":comp"); menu != null; menu = get(
					menu, ":next")) {
				Dimension d = getSize(menu, 8, 4);
				size.width += d.width;
				size.height = Math.max(size.height, d.height);
			}
			return size;
		}
		throw new IllegalArgumentException(classname);
	}

	/**
	 * For the starting component search its parent direction for a focusable
	 * component, and then its next component (if not search backward from the
	 * component). <br />
	 * For its parent components check its first component, the current one, and
	 * its parent direction (backward search), or its parent, then next
	 * component (forward direction). <br />
	 * For the rest components check the next, then the first subcomponent
	 * direction, and finally check whether the component is focusable.
	 */
	private Object getPreviousFocusable(Object component, Object block,
			boolean start, boolean upward, boolean backward, boolean outgo) {
		Object previous = null;
		if ((component != null) && (component != block)) {
			boolean go = ((getClass(component) != "dialog") || (outgo && !getBoolean(
					component, "modal", false)));
			if (!start && !upward && go) {
				previous = getPreviousFocusable(get(component, ":next"), block,
						false, false, backward, outgo);
			}
			if ((previous == null)
					&& ((upward && backward) || (!start && !upward))) {
				previous = getPreviousFocusable(get(component, ":comp"), block,
						false, false, backward, outgo);
				if ((previous == null) && isFocusable(component, false)) {
					previous = component;
				}
			}
			if ((previous == null) && (start || upward) && go) {
				previous = getPreviousFocusable(getParent(component),
						component, false, true, backward, outgo);
			}
			if ((previous == null) && (start || upward) && !backward && go) {
				previous = getPreviousFocusable(get(component, ":next"), block,
						false, false, backward, outgo);
			}
		}
		return previous;
	}

	/**
	 * Returns the value of the property with the specified key.
	 * 
	 * @param component
	 *            searches the hashtable of this component
	 * @param key
	 *            the client property key
	 * @return the value to which the key is mapped or null if the key is not
	 *         mapped to any value
	 */
	public Object getProperty(Object component, Object key) {
		Object table = get(component, ":bind");
		return (table != null) ? ((Hashtable) table).get(key) : null;
	}

	/**
	 *  
	 */
	protected Rectangle getRectangle(Object component, String key) {
		return (Rectangle) get(component, key);
	}

	/**
	 * Gets the index of the first selected item in the given component
	 * 
	 * @param component
	 *            a widget (combobox, tabbedpane, list, table, header, or tree)
	 * @return the first selected index or -1
	 */
	public int getSelectedIndex(Object component) {
		String classname = getClass(component);
		if ((classname == "combobox") || (classname == "tabbedpane")) {
			return getInteger(component, "selected",
					(classname == "combobox") ? -1 : 0);
		}
		if ((classname == "list") || (classname == "table")
				|| (classname == "header") || (classname == "tree")) {
			Object item = get(component, ":comp");
			for (int i = 0; item != null; i++) {
				if (getBoolean(item, "selected", false)) {
					return i;
				}
				item = get(item, ":next");
			}
			return -1;
		}
		throw new IllegalArgumentException(classname);
	}

	/**
	 * Gets the first selected item of the given component
	 * 
	 * @param component
	 *            a widget (combobox, tabbedpane, list, table, header or tree)
	 * @return the first selected item or null
	 */
	public Object getSelectedItem(Object component) {
		String classname = getClass(component);
		if ((classname == "combobox") || (classname == "tabbedpane")) {
			int index = getInteger(component, "selected",
					(classname == "combobox") ? -1 : 0);
			return (index != -1) ? getItemImpl(component, ":comp", index)
					: null;
		}
		if ((classname == "list") || (classname == "table")
				|| (classname == "header") || (classname == "tree")) {
			for (Object item = findNextItem(component, classname, null); item != null; item = findNextItem(
					component, classname, item)) {
				if (getBoolean(item, "selected", false)) {
					return item;
				}
			}
			return null;
		}
		throw new IllegalArgumentException(classname);
	}

	/**
	 * Gets the selected item of the given component (list, table, or tree) when
	 * multiple selection is allowed
	 * 
	 * @param component
	 *            a widget
	 * @return the array of selected items, or a 0 length array
	 */
	public Object[] getSelectedItems(Object component) {
		String classname = getClass(component);
		Object[] selecteds = new Object[0];
		for (Object item = findNextItem(component, classname, null); item != null; item = findNextItem(
				component, classname, item)) {
			if (getBoolean(item, "selected", false)) {
				Object[] temp = new Object[selecteds.length + 1];
				System.arraycopy(selecteds, 0, temp, 0, selecteds.length);
				temp[selecteds.length] = item;
				selecteds = temp;
			}
		}
		return selecteds;
	}

	/**
	 * @param component
	 *            a widget including the text and icon parameters
	 * @param dx
	 *            increase width by this value
	 * @param dy
	 *            increase height by this value
	 * @return size of the text and the image (plus a gap) including the given
	 *         offsets
	 */
	private Dimension getSize(Object component, int dx, int dy) {
		String text = getString(component, "text", null);
		int tw = 0;
		int th = 0;
		if (text != null) {
			//SH
			// Font customfont = (Font) get(component, "font");
			Font customfont = font;
			if (customfont == null)
				customfont = font;
			tw = customfont.stringWidth(text);
			th = customfont.getHeight();
		}
		Image icon = getIcon(component, "icon", null);
		int iw = 0;
		int ih = 0;
		if (icon != null) {
			iw = icon.getWidth();
			ih = icon.getHeight();
			if (text != null) {
				iw += 2;
			}
		}
		return new Dimension(tw + iw + dx, Math.max(th, ih) + dy); 
	}

	/**
	 * Gets the property value of the given component by the property key
	 */
	public String getString(Object component, String key) {
		return (String) get(component, key, "string");

	}

	/**
	 *  
	 */
	public String getString(Object component, String key, String defaultvalue) {
		Object value = get(component, key);
		return (value == null) ? defaultvalue : getI18NString(component, key,
				(String) value);
	}

	/**
	 *  
	 */
	private int getSum(int[] values, int from, int length, int gap, boolean last) {
		if (length <= 0) {
			return 0;
		}
		int value = 0;
		for (int i = 0; i < length; i++) {
			value += values[from + i];
		}
		return value + (length - (last ? 0 : 1)) * gap;
	}

	//TODO add set/getComponent for popupmenu and header

	/**
	 *  
	 */
	public Object getWidget(Object component, String key) {
		if ("popupmenu".equals(key)) {
			return get(component, "popupmenu");
		} else if ("header".equals(key)) {
			return get(component, "header");
		} else
			throw new IllegalArgumentException(key);
	}

	protected int getWidth(Object component) {
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds == null) {
			Dimension dim = getPreferredSize(component);
			bounds = new Rectangle(0, 0, dim.width, dim.height);
			set(component, "bounds", bounds);
		}
		return bounds.width;
	}

	/*
	 * protected boolean invoke(Object component, Object part, String event) {
	 * String action = (String) get(component, event); if (action != null) {
	 * return handle(component, part, action); } return false; }
	 */

	protected boolean handle(Object source, Object part, String action,
			Object[] args) {
		return true;
	}

	/**
	 * Overwrite this method to handle exceptions thrown by the invoked custom
	 * methods
	 * 
	 * @param throwable
	 *            the thrown exception by the bussiness logic
	 */
	protected void handleException(Throwable throwable) {
		throwable.printStackTrace();
	}

	/**
	 * @param x
	 *            mouse x position relative to thinlet component
	 * @param y
	 *            mouse y position relative to the main desktop
	 */
	private void handleMouseEvent(int x, int y, int clickcount,
			boolean shiftdown, boolean controldown, boolean popuptrigger,
			int id, Object component, Object part) {
		if (id == MOUSE_ENTERED) {
			setTimer(750L);
		} else if (id == MOUSE_EXITED) {
			hideTip();
		}
		if (!getBoolean(component, "enabled", true)) {
			return;
		}
		String classname = getClass(component);

		if (("list" == classname) || ("table" == classname)
				|| ("tree" == classname)) {
			// check if we need to change the column selection and invoke the
			// header's
			// action
			Object header = get(component, "header");
			boolean noScroll = false;
			if (header != null && get(header, ":resizecomponent") != null) {
				noScroll = true;
				if (id == MOUSE_PRESSED) {
					referencex = x;
					set(header, ":resizing", "true");
				} else if (id == MOUSE_DRAGGED) {
					//resize the column, but limit its minimum size to a width
					// of 10
					Object column = get(header, ":resizecomponent");
					int newSize = getInteger(column, "width") + x - referencex;
					if (newSize > 10) {
						setInteger(column, "width", newSize);
						referencex = x;
						doLayout(component);
						repaint(component);
					}
				} else if (id == MOUSE_RELEASED)
					set(header, ":resizing", null);
				//else if (id == MOUSE_ENTERED)
				// setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
			}
			if (header != null && get(header, ":resizecomponent") == null) {
				set(header, ":resizing", null);
				//setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
			if (header != null && get(header, "action") != null) {
				if (insidepart != null && insidepart instanceof Object[]
						&& "column" == getClass(insidepart)) {
					noScroll = true;
					if (id == MOUSE_ENTERED || id == MOUSE_PRESSED
							|| id == MOUSE_RELEASED) {
						if (id == MOUSE_RELEASED && mouseinside == component) {
							//set selected column its sort and selected
							// property
							//and set the sort of all other columns to none
							// (null)
							Object column = get(get(component, "header"),
									":comp");
							Object sort = null;
							while (column != null) {
								if (column == insidepart) {
									sort = get(column, "sort");
									if (null == sort || "none" == sort
											|| "descent" == sort)
										sort = "ascent";
									else if ("ascent" == sort)
										sort = "descent";
								} else
									sort = null;
								set(column, "sort", sort);
								setBoolean(column, "selected", sort != null
										&& sort != "none");
								column = get(column, ":next");
							}
							invoke(header, null, "action");
						}
						repaint(component);
					}
				} else if (id == MOUSE_EXITED)
					repaint(component);
			}
			if (!noScroll && !processScroll(x, y, id, component, part)) {
				if (((id == MOUSE_PRESSED) || ((id == MOUSE_DRAGGED)
						&& !shiftdown && !controldown))) {
					//Rectangle view = getRectangle(component, ":view");
					Rectangle port = getRectangle(component, ":port");
					int my = y + port.y - referencey;
					for (Object item = get(component, ":comp"); item != null;) {
						Rectangle r = getRectangle(item, "bounds");
						if (my < r.y + r.height) {
							if (id == MOUSE_DRAGGED) { //!!!
								scrollToVisible(component, r.x, r.y, 0,
										r.height);
							} else if ("tree" == classname) {
								int mx = x + port.x - referencex;
								if (mx < r.x) {
									if ((mx >= r.x - block)
											&& (get(item, ":comp") != null)) {
										boolean expanded = getBoolean(item,
												"expanded", true);
										setBoolean(item, "expanded", !expanded,
												true);
										selectItem(component, item, true);
										setLead(component, get(component,
												":lead"), item);
										setFocus(component);
										validate(component);
										invoke(component, item,
												expanded ? "collapse"
														: "expand"); //item
									}
									break;
								}
							}
							if ((id != MOUSE_DRAGGED)
									|| !getBoolean(item, "selected", false)) {
								if (id != MOUSE_DRAGGED) {
									if (setFocus(component)) {
										repaint(component, classname, item);
									} //?
								}
								select(component, item, ("tree" == classname),
										shiftdown, controldown);
								if (clickcount == 1) {
									invoke(component, item, "perform");
								}
							}
							break;
						}
						item = getNextItem(component, item,
								("tree" == classname));
					}
				}
			}
		} else if (("button" == classname) || ("checkbox" == classname)
				|| ("togglebutton" == classname)) {
			if ((id == MOUSE_ENTERED) || (id == MOUSE_EXITED)
					|| (id == MOUSE_PRESSED) || (id == MOUSE_RELEASED)) {
				if (id == MOUSE_PRESSED) {
					setFocus(component);
				}
				if (("button" == classname)
						&& ((mousepressed == null) || (mousepressed == component))
						&& ((id == MOUSE_ENTERED) || (id == MOUSE_EXITED))
						&& (get(component, "type") == "link")) {
					//setCursor(Cursor.getPredefinedCursor((id ==
					// MOUSE_ENTERED) ? Cursor.HAND_CURSOR
					//        : Cursor.DEFAULT_CURSOR));
				} else if ((id == MOUSE_RELEASED) && (mouseinside == component)) {
					if ("button" != classname) {
						changeCheck(component, true);
					} else
						invoke(component, null, "action");
				}
				repaint(component);
			}
		} else if ("combobox" == classname) {
			boolean editable = getBoolean(component, "editable", true);
			if (editable && (part == null)) { // textfield area
				Image icon = null;
				int left = ((id == MOUSE_PRESSED) && ((icon = getIcon(
						component, "icon", null)) != null)) ? icon.getWidth()
						: 0;
				processField(x, y, clickcount, id, component, false, false,
						left);
			} else if (part != "icon") { // part = "down"
				if (((id == MOUSE_ENTERED) || (id == MOUSE_EXITED))
						&& (mousepressed == null)) {
					if (editable) {
						repaint(component, "combobox", part);
					} // hover the arrow button
					else {
						repaint(component);
					} // hover the whole combobox
				} else if (id == MOUSE_PRESSED) {
					Object combolist = get(component, ":combolist");
					if (combolist == null) { // combolist is closed
						setFocus(component);
						repaint(component);
						popupCombo(component);
					} else { // combolist is visible
						closeCombo(component, combolist, null);
					}
				} else if (id == MOUSE_RELEASED) {
					if (mouseinside != component) {
						Object combolist = get(component, ":combolist");
						closeCombo(
								component,
								combolist,
								((mouseinside == combolist) && (insidepart instanceof Object[])) ? insidepart
										: null);
					} else {
						repaint(component);
					}
				}
			}
		} else if (":combolist" == classname) {
			if (!processScroll(x, y, id, component, part)) {
				if ((id == MOUSE_ENTERED) || (id == DRAG_ENTERED)) {
					if (part != null) { //+ scroll if dragged
						setInside(component, part, false);
					}
				} else if (id == MOUSE_RELEASED) {
					closeCombo(get(component, "combobox"), component, part);
				}
			}
		} else if (("textfield" == classname) || ("passwordfield" == classname)) {
			processField(x, y, clickcount, id, component, false,
					("passwordfield" == classname), 0);
		} else if ("textarea" == classname) {
			if (!processScroll(x, y, id, component, part)) {
				processField(x, y, clickcount, id, component, true, false, 0);
			}
		} else if ("panel" == classname) {
			processScroll(x, y, id, component, part);
		} else if ("desktop" == classname) {
			if (part == "modal") {
				if (id == MOUSE_ENTERED) {
					//setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
				} else if (id == MOUSE_EXITED) {
					//setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				}
			}
		} else if ("spinbox" == classname) {
			if (part == null) {
				processField(x, y, clickcount, id, component, false, false, 0);
			} else { // part = "up" || "down"
				if ((id == MOUSE_ENTERED) || (id == MOUSE_EXITED)
						|| (id == MOUSE_PRESSED) || (id == MOUSE_RELEASED)) {
					if (id == MOUSE_PRESSED) {
						setFocus(component);
						if (processSpin(component, part)) {
							setTimer(375L);
						}
						//settext: start end selection, parse exception...
					} else {
						if (id == MOUSE_RELEASED) {
							setTimer(0L);
						}
					}
					repaint(component, classname, part);
				}
			}
		} else if ("tabbedpane" == classname) {
			if ((id == MOUSE_ENTERED) || (id == MOUSE_EXITED)) {
				if ((part != null)
						&& getBoolean(part, "enabled", true)
						&& (getInteger(component, "selected", 0) != getIndex(
								component, part))) {
					repaint(component, "tabbedpane", part);
				}
			} else if ((part != null) && (id == MOUSE_PRESSED)
					&& getBoolean(part, "enabled", true)) {
				int selected = getInteger(component, "selected", 0);
				int current = getIndex(component, part);
				if (selected == current) {
					setFocus(component);
					repaint(component, "tabbedpane", part);
				} else {
					setInteger(component, "selected", current, 0);
					//Object tabcontent = getItem(component, current);
					//setFocus((tabcontent != null) ? tabcontent : component);
					setNextFocusable(component, false);
					checkOffset(component);
					repaint(component);
					invoke(component, part, "action");
				}
			}
		} else if ("slider" == classname) {
			if ((id == MOUSE_PRESSED) || (id == MOUSE_DRAGGED)) {
				if (id == MOUSE_PRESSED) {
					setReference(component, block / 2, block / 2);
					setFocus(component);
				}
				int minimum = getInteger(component, "minimum", 0);
				int maximum = getInteger(component, "maximum", 100);
				int value = getInteger(component, "value", 50);
				Rectangle bounds = getRectangle(component, "bounds");
				boolean horizontal = ("vertical" != get(component,
						"orientation"));
				int newvalue = minimum
						+ (horizontal ? (x - referencex) : (y - referencey))
						* (maximum - minimum)
						/ ((horizontal ? bounds.width : bounds.height) - block); //...
				// +0.5
				newvalue = Math.max(minimum, Math.min(newvalue, maximum));
				if (value != newvalue) {
					setInteger(component, "value", newvalue, 50);
					invoke(component, null, "action");
				}
				if ((value != newvalue) || (id == MOUSE_PRESSED)) {
					repaint(component);
				}
			}
		} else if ("splitpane" == classname) {
			if (id == MOUSE_PRESSED) {
				setReference(component, 2, 2);
			} else if (id == MOUSE_DRAGGED) {
				int divider = getInteger(component, "divider", -1);
				boolean horizontal = ("vertical" != get(component,
						"orientation"));
				int moveto = horizontal ? (x - referencex) : (y - referencey);
				Rectangle bounds = getRectangle(component, "bounds");
				moveto = Math.max(0, Math.min(moveto, Math
						.abs(horizontal ? bounds.width : bounds.height) - 5));
				if (divider != moveto) {
					setInteger(component, "divider", moveto, -1);
					validate(component);
				}
				/*
				 * } else if ((id == MOUSE_ENTERED) && (mousepressed == null)) {
				 * boolean horizontal = ("vertical" != get(component,
				 * "orientation"));
				 * setCursor(Cursor.getPredefinedCursor(horizontal ?
				 * Cursor.E_RESIZE_CURSOR : Cursor.S_RESIZE_CURSOR)); } else if
				 * (((id == MOUSE_EXITED) && (mousepressed == null)) || ((id ==
				 * MOUSE_RELEASED) && (mouseinside != component))) {
				 * setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				 */
			}
		} else if ("menubar" == classname) {
			Object selected = get(component, "selected");
			if (((id == MOUSE_ENTERED) || (id == MOUSE_EXITED))
					&& (part != null) && (selected == null)
					&& getBoolean(part, "enabled", true)) {
				repaint(component, classname, part);
			} else if ((part != null)
					&& ((selected == null) ? (id == MOUSE_PRESSED)
							: ((id == MOUSE_ENTERED) || (id == DRAG_ENTERED)))
					&& getBoolean(part, "enabled", true)) {
				// || ((id == MOUSE_PRESSED) && (insidepart != part))
				set(component, "selected", part);
				popupMenu(component);
				repaint(component, classname, part);
			} else if ((id == MOUSE_PRESSED) && (selected != null)) {
				closeup();
			} else if (id == MOUSE_RELEASED) {
				if ((part != insidepart)
						&& ((insidepart == null) || ((insidepart instanceof Object[]) && (getClass(insidepart) != "menu")))) {
					if ((insidepart != null)
							&& getBoolean(insidepart, "enabled", true)) {
						if (getClass(insidepart) == "checkboxmenuitem") {
							changeCheck(insidepart, false);
						} else
							invoke(insidepart, null, "action");
					}
					closeup();
				}
			}
		} else if (":popup" == classname) {
			if (part != null) {
				if (((id == MOUSE_ENTERED) || (id == DRAG_ENTERED))
						&& getBoolean(part, "enabled", true)) {
					set(component, "selected", part);
					popupMenu(component);
					repaint(component, classname, part);
				} else if (id == MOUSE_RELEASED) {
					if ((insidepart == null)
							|| (getClass(insidepart) != "menu")) {
						if ((insidepart != null)
								&& getBoolean(insidepart, "enabled", true)) {
							if (getClass(insidepart) == "checkboxmenuitem") {
								changeCheck(insidepart, false);
							} else
								invoke(insidepart, null, "action");
						}
						closeup();
					}
				} else if (((id == MOUSE_EXITED) || (id == DRAG_EXITED))
						&& getBoolean(part, "enabled", true)) {
					if (getClass(part) != "menu") {
						set(component, "selected", null);
					}
					repaint(component, classname, part);
				}
			}
		} else if ("dialog" == classname) {
			if (part == "header") {
				if (id == MOUSE_PRESSED) {
					Rectangle bounds = getRectangle(component, "bounds");
					referencex = x - bounds.x;
					referencey = y - bounds.y;
					Object parent = getParent(component);
					if (get(parent, ":comp") != component) { // to front
						removeItemImpl(parent, component);
						insertItem(parent, ":comp", component, 0);
						set(component, ":parent", parent);
						repaint(component); // to front always...
						setNextFocusable(component, false);
					}
				} else if (id == MOUSE_DRAGGED) {
					Rectangle bounds = getRectangle(component, "bounds");
					Rectangle parents = getRectangle(getParent(component),
							"bounds");
					int mx = Math.max(0, Math.min(x - referencex, parents.width
							- bounds.width));
					int my = Math.max(0, Math.min(y - referencey,
							parents.height - bounds.height));
					if ((bounds.x != mx) || (bounds.y != my)) {
						// repaint the union of the previous and next bounds
						repaint(component, Math.min(bounds.x, mx), Math.min(
								bounds.y, my), bounds.width
								+ Math.abs(mx - bounds.x), bounds.height
								+ Math.abs(my - bounds.y));
						bounds.x = mx;
						bounds.y = my;
					}
				}
			}
			// close dialog button
			else if (part == ":closebutton") {
				if (id == MOUSE_ENTERED || id == MOUSE_EXITED
						|| id == MOUSE_PRESSED || id == MOUSE_RELEASED) {
					if (id == MOUSE_RELEASED && mouseinside == component)
						invoke(component, null, "close");
					repaint(component);
				}
			} else if (!processScroll(x, y, id, component, part)
					&& (part != null)) {
				if (id == MOUSE_PRESSED) {
					referencex = x;
					referencey = y;
				} else if (id == MOUSE_DRAGGED) {
					repaint(component);
					Rectangle bounds = getRectangle(component, "bounds");
					if ((part == ":nw") || (part == ":n") || (part == ":ne")) {
						bounds.y += y - referencey;
						bounds.height -= y - referencey;
					}
					if ((part == ":ne") || (part == ":e") || (part == ":se")) {
						bounds.width += x - referencex;
					}
					if ((part == ":sw") || (part == ":s") || (part == ":se")) {
						bounds.height += y - referencey;
					}
					if ((part == ":nw") || (part == ":w") || (part == ":sw")) {
						bounds.x += x - referencex;
						bounds.width -= x - referencex;
					}
					referencex = x;
					referencey = y;
					doLayout(component);
					repaint(component);
					/*
					 * } else if (id == MOUSE_ENTERED) {
					 * setCursor(Cursor.getPredefinedCursor((part == ":n") ?
					 * Cursor.N_RESIZE_CURSOR : (part == ":ne") ?
					 * Cursor.NE_RESIZE_CURSOR : (part == ":e") ?
					 * Cursor.E_RESIZE_CURSOR : (part == ":se") ?
					 * Cursor.SE_RESIZE_CURSOR : (part == ":s") ?
					 * Cursor.S_RESIZE_CURSOR : (part == ":sw") ?
					 * Cursor.SW_RESIZE_CURSOR : (part == ":w") ?
					 * Cursor.W_RESIZE_CURSOR : Cursor.NW_RESIZE_CURSOR)); }
					 * else if (id == MOUSE_EXITED) {
					 * setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
					 */
				}
			}
		}

		if (popuptrigger) {// && (id == MOUSE_RELEASED)) {
			Object popupmenu = get(component, "popupmenu");
			if (popupmenu != null) {
				popupPopup(popupmenu, x, y);
			}
		}
	}

	/**
	 * @param component
	 * @param keycode
	 * @param modifiers
	 * @return true if the component has the given accelerator
	 */
	private boolean hasAccelerator(Object component, int keycode, int modifiers) {
		Object accelerator = get(component, "accelerator");
		if (accelerator != null) {
			long keystroke = ((Long) accelerator).longValue();
			return ((keystroke >> 32) == modifiers)
					&& ((keystroke & 0xffff) == keycode);
		}
		return false;
	}

	/**
	 * @param component
	 * @param keycode
	 * @param modifiers
	 * @return true if the component has the given mnemonic
	 */
	private boolean hasMnemonic(Object component, int keycode, int modifiers) {
		/*
		 * if (modifiers == InputEvent.ALT_MASK) { int index =
		 * getInteger(component, "mnemonic", -1); if (index != -1) { String text =
		 * getString(component, "text", null); return (text != null) &&
		 * (text.length() > index) && (Character.toUpperCase(text.charAt(index)) ==
		 * keycode); } }
		 */
		return false;
	}

	/**
	 *  
	 */
	private void hideTip() {
		if (tooltipowner != null) {
			Rectangle bounds = getRectangle(tooltipowner, ":tooltipbounds");
			set(tooltipowner, ":tooltipbounds", null);
			tooltipowner = null;
			repaint(bounds.x, bounds.y, bounds.width, bounds.height);
		}
	}

	/**
	 * Referenced by DOM
	 */
	private void insertItem(Object parent, Object key, Object component,
			int index) {
		Object item = parent, next = get(parent, key);
		for (int i = 0;; i++) {
			if ((i == index) || (next == null)) {
				set(item, key, component);
				set(component, ":next", next);
				break;
			}
			next = get(item = next, key = ":next");
		}
	}

	/**
	 *  
	 */
	private boolean instance(Object classname, Object extendclass) {
		if (classname == extendclass) {
			return true;
		}
		for (int i = 0; i < dtd.length; i += 3) {
			if (classname == dtd[i]) {
				return instance(dtd[i + 1], extendclass);
			}
		}
		return false;
	}

	/**
	 *  
	 */
	protected boolean invoke(Object component, Object part, String event) {
		Object method = get(component, event);
		if (method != null) {
			if (focusowner != null && "action".equals(event))
				requestFocus(component);
			invokeImpl(method, part);
			return true;
		}
		return false;
	}

	/**
	 *  
	 */

	protected void invokeImpl(Object method, Object part) {
		/* Object[] */data = (Object[]) method;
		/* Object[] */args = (data.length > 2) ? new Object[(data.length - 2) / 3]
				: null;
		if (args != null)
			for (int i = 0; i < args.length; i++) {
				Object target = data[2 + 3 * i];
				if ("thinlet".equals(target)) {
					args[i] = this;
				} else {
					if ("item".equals(target)) {
						target = part;
					}
					Object parametername = data[2 + 3 * i + 1];
					if (parametername == null) {
						args[i] = target;
						//args[i] = new Widget(this, target);
					} else {
						args[i] = (target != null) ? get(target, parametername)
								: null;
						if (args[i] == null) {
							args[i] = data[2 + 3 * i + 2];
						}
					}
				}
			}
		try {
			handle(data[0], part, (String) data[1], args);

			//  ((Method) data[1]).invoke(data[0], args);
			//} catch (InvocationTargetException ite) {
			//    handleException(ite.getTargetException());
		} catch (Throwable throwable) {
			handleException(throwable);
		}
	}

	/**
	 * Check whether the given widget can become focusowner
	 * 
	 * @param component
	 *            check this widget
	 * @param forced
	 *            splitpane is also checked (e.g. false for tab navigating, and
	 *            true for mouse selection or application request)
	 * @return true if focusable, otherwise false
	 */
	private boolean isFocusable(Object component, boolean forced) {
		String classname = getClass(component);
		if ((classname == "menubar") || (classname == "button")
				|| (classname == "checkbox") || ("togglebutton" == classname)
				|| (classname == "combobox") || (classname == "textfield")
				|| (classname == "passwordfield") || (classname == "textarea")
				|| (classname == "spinbox") || (classname == "slider")
				|| (classname == "list") || (classname == "table")
				|| (classname == "tree") || (classname == "tabbedpane")
				|| (forced && (classname == "splitpane"))) {
			for (Object comp = component; comp != null;) {
				// component and parents are enabled and visible
				if (!getBoolean(comp, "enabled", true)
						|| !getBoolean(comp, "visible", true)) {
					return false;
				}
				Object parent = getParent(comp);
				// inside the selected tabbedpane tab
				if ((getClass(comp) == "tab")
						&& (getItem(parent, getInteger(parent, "selected", 0)) != comp)) {
					return false;
				}
				comp = parent;
			}
			return true;
		}
		return false;
	}

	/**
	 * This component can be traversed using Tab or Shift-Tab keyboard focus
	 * traversal, although 1.4 replaced this method by <i>isFocusable </i>, so
	 * 1.4 compilers write deprecation warning
	 * 
	 * @return true as focus-transverable component, overwrites the default
	 *         false value
	 */
	public boolean isFocusTraversable() {
		return true;
	}

	protected void keyPressed(int keyCode) {
		System.out.println("key=" + keyCode + " (" + (char) keyCode + ")");
		if ((popupowner != null) || (focusowner != null)) {
			hideTip();
			if (keyCode > 0) {
				processKeyPress((popupowner != null) ? popupowner : focusowner,
						false, false, 1, keyCode, 0);
			} else {
				int keychar = 0, key = 0;
				switch (getGameAction(keyCode)) {
				case UP:
					key = KeyEvent.VK_UP;
					break;
				case LEFT:
					key = KeyEvent.VK_LEFT;
					break;
				case DOWN:
					key = KeyEvent.VK_DOWN;
					break;
				case RIGHT:
					key = KeyEvent.VK_RIGHT;
					break;
				case FIRE:
					key = KeyEvent.VK_ENTER;
					keychar = KeyEvent.VK_SPACE;
					break;
				case GAME_A:
					key = KeyEvent.VK_ESCAPE;
					break;
				}
				if (key != 0) {
					processKeyPress((popupowner != null) ? popupowner
							: focusowner, false, false, 1, keychar, key);
				}
				//if (keyCode == getKeyCode(LEFT)) {
			}
		}
	}

	protected void keyRepeated(int keyCode) {
		keyPressed(keyCode);
	}

	/**
	 *  
	 */
	private void layoutField(Object component, int dw, boolean hidden, int left) {
		int width = getRectangle(component, "bounds").width - left - dw;
		String text = getString(component, "text", "");
		int start = getInteger(component, "start", 0);
		if (start > text.length()) {
			setInteger(component, "start", start = text.length(), 0);
		}
		int end = getInteger(component, "end", 0);
		if (end > text.length()) {
			setInteger(component, "end", end = text.length(), 0);
		}
		int offset = getInteger(component, ":offset", 0);
		int off = offset;
		//SH
		//Font currentfont = (Font) get(component, "font");
		Font currentfont = font;
		if (currentfont == null)
			currentfont = font;
		int caret = hidden ? (currentfont.charWidth('*') * end) : currentfont
				.stringWidth(text.substring(0, end));
		if (off > caret) {
			off = caret;
		} else if (off < caret - width + 4) {
			off = caret - width + 4;
		}
		off = Math.max(0, Math.min(off,
				(hidden ? (currentfont.charWidth('*') * text.length())
						: currentfont.stringWidth(text))
						- width + 4));
		if (off != offset) {
			setInteger(component, ":offset", off, 0);
		}
	}

	/**
	 * Set viewport (:port) bounds excluding borders, view position and content
	 * size (:view), horizontal (:horizontal), and vertical (:vertical)
	 * scrollbar bounds
	 * 
	 * @param component
	 *            scrollable widget
	 * @param contentwidth
	 *            preferred component width
	 * @param contentheight
	 *            preferred component height
	 * @param top
	 *            top inset (e.g. table header, dialog title, half of panel
	 *            title)
	 * @param left
	 *            left inset (e.g. dialog border)
	 * @param bottom
	 *            bottom inset (e.g. dialog border)
	 * @param right
	 *            right inset (e.g. dialog border)
	 * @param topgap
	 *            (lower half of panel title)
	 * @return true if scrollpane is required, otherwise false
	 * 
	 * list: 0, 0, 0, 0, true, 0 | table: header, ... | dialog: header, 3, 3, 3,
	 * true, 0 title-border panel: header / 2, 0, 0, 0, true, head
	 */
	private boolean layoutScroll(Object component, int contentwidth,
			int contentheight, int top, int left, int bottom, int right,
			boolean border, int topgap) {
		Rectangle bounds = getRectangle(component, "bounds");
		int iborder = border ? 1 : 0;
		int iscroll = block + 1 - iborder;
		int portwidth = bounds.width - left - right - 2 * iborder; // available
		// horizontal
		// space
		int portheight = bounds.height - top - topgap - bottom - 2 * iborder; // vertical
		// space
		boolean hneed = contentwidth > portwidth; // horizontal scrollbar
		// required
		boolean vneed = contentheight > portheight - (hneed ? iscroll : 0); // vertical
		// scrollbar
		// needed
		if (vneed) {
			portwidth -= iscroll;
		} // subtract by vertical scrollbar width
		hneed = hneed || (vneed && (contentwidth > portwidth));
		if (hneed) {
			portheight -= iscroll;
		} // subtract by horizontal scrollbar height

		setRectangle(component, ":port", left + iborder,
				top + iborder + topgap, portwidth, portheight);
		if (hneed) {
			setRectangle(component, ":horizontal", left, bounds.height - bottom
					- block - 1, bounds.width - left - right
					- (vneed ? block : 0), block + 1);
		} else {
			set(component, ":horizontal", null);
		}
		if (vneed) {
			setRectangle(component, ":vertical", bounds.width - right - block
					- 1, top, block + 1, bounds.height - top - bottom
					- (hneed ? block : 0));
		} else {
			set(component, ":vertical", null);
		}

		contentwidth = Math.max(contentwidth, portwidth);
		contentheight = Math.max(contentheight, portheight);
		int viewx = 0, viewy = 0;
		Rectangle view = getRectangle(component, ":view");
		if (view != null) { // check the previous location
			viewx = Math.max(0, Math.min(view.x, contentwidth - portwidth));
			viewy = Math.max(0, Math.min(view.y, contentheight - portheight));
		}
		setRectangle(component, ":view", viewx, viewy, contentwidth,
				contentheight);
		return vneed || hneed;
	}

	/*
	 * public boolean imageUpdate(Image img, int infoflags, int x, int y, int
	 * width, int height) { if (infoflags == ImageObserver.ALLBITS) {
	 * validate(content); } return super.imageUpdate(img, infoflags, x, y,
	 * width, height); }
	 */

	public void paint(Graphics g) {
		if (hgradient == null) {
			hgradient = Image.createImage(block, block);
			vgradient = Image.createImage(block, block);
			int r1 = Color.getRed(c_bg);
			int r2 = Color.getRed(c_press);
			int g1 = Color.getGreen(c_bg);
			int g2 = Color.getGreen(c_press);
			int b1 = Color.getBlue(c_bg);
			int b2 = Color.getBlue(c_press);
			Graphics hg = hgradient.getGraphics();
			Graphics vg = vgradient.getGraphics();
			for (int i = 0; i < block; i++) {
				int cr = r1 - (r1 - r2) * i / block;
				int cg = g1 - (g1 - g2) * i / block;
				int cb = b1 - (b1 - b2) * i / block;
				int color = (255 << 24) | (cr << 16) | (cg << 8) | cb;
				hg.setColor(color);
				vg.setColor(color);
				hg.drawLine(0, i, block - 1, i);
				vg.drawLine(i, 0, i, block - 1);
			}
		}
		paint(g, g.getClipX(), g.getClipY(), g.getClipWidth(), g
				.getClipHeight(), content, true);
	}

	/**
	 * @param clipx
	 *            the cliping rectangle is relative to the component's parent
	 *            location similar to the component's bounds rectangle
	 * @param clipy
	 * @param clipwidth
	 * @param clipheight
	 * @throws java.lang.IllegalArgumentException
	 */
	private void paint(Graphics g, int clipx, int clipy, int clipwidth,
			int clipheight, Object component, boolean enabled) {
		if (!getBoolean(component, "visible", true)) {
			return;
		}
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds == null) {
			return;
		}
		// negative component width indicates invalid component layout
		if (bounds.width < 0) {
			bounds.width = Math.abs(bounds.width);
			doLayout(component);
		}
		// return if the component was out of the cliping rectangle
		if ((clipx + clipwidth < bounds.x) || (clipx > bounds.x + bounds.width)
				|| (clipy + clipheight < bounds.y)
				|| (clipy > bounds.y + bounds.height)) {
			return;
		}
		// set the clip rectangle relative to the component location
		clipx -= bounds.x;
		clipy -= bounds.y;
		g.translate(bounds.x, bounds.y);
		//g.setClip(0, 0, bounds.width, bounds.height);
		String classname = getClass(component);
		boolean pressed = (mousepressed == component);
		boolean inside = (mouseinside == component)
				&& ((mousepressed == null) || pressed);
		boolean focus = focusinside && (focusowner == component);
		enabled = getBoolean(component, "enabled", true); //enabled &&

		if ("label" == classname) {
			paint(component, 0, 0, bounds.width, bounds.height, g, clipx,
					clipy, clipwidth, clipheight, false, false, false, false,
					0, 0, 0, 0, false, enabled ? 'e' : 'd', "left", true, false);
		} else if (("button" == classname) || ("togglebutton" == classname)) {
			boolean toggled = ("togglebutton" == classname)
					&& getBoolean(component, "selected", false);
			boolean link = ("button" == classname)
					&& (get(component, "type") == "link");
			if (link) {
				paint(component, 0, 0, bounds.width, bounds.height, g, clipx,
						clipy, clipwidth, clipheight, false, false, false,
						false, 0, 0, 0, 0, focus, enabled ? (pressed ? 'e'
								: 'l') : 'd', "center", true, enabled
								&& (inside != pressed));
			} else { // disabled toggled
				char mode = enabled ? ((inside != pressed) ? 'h'
						: ((pressed || toggled) ? 'p' : 'g')) : 'd';
				paint(component, 0, 0, bounds.width, bounds.height, g, clipx,
						clipy, clipwidth, clipheight, true, true, true, true,
						2, 5, 2, 5, focus, mode, "center", true, false);
				//(enabled && ("button" == classname) && get(component, "type")
				// ==
				// "default")...
			}
		} else if ("checkbox" == classname) {
			paint(component, 0, 0, bounds.width, bounds.height, g, clipx,
					clipy, clipwidth, clipheight, false, false, false, false,
					0, block + 3, 0, 0, false, enabled ? 'e' : 'd', "left",
					true, false);

			boolean selected = getBoolean(component, "selected", false);
			String group = getString(component, "group", null);
			int border = enabled ? c_border : c_disable;
			int foreground = enabled ? ((inside != pressed) ? c_hover
					: (pressed ? c_press : c_ctrl)) : c_bg;
			int dy = (bounds.height - block + 2) / 2;
			if (group == null) {
				paintRect(g, 1, dy + 1, block - 2, block - 2, border,
						foreground, true, true, true, true, true);
			} else {
				g.setColor((foreground != c_ctrl) ? foreground : c_bg);
				g.fillRect(1, dy + 1, block - 3 + evm, block - 3 + evm);//SH
				g.setColor(border);
				g.drawRect(1, dy + 1, block - 3, block - 3);//SH
			}
			if (focus) {
				g.setColor(c_focus);
				if (group == null) {
					g.drawRect(3, dy + 3, block - 7, block - 7);
				} else {
					g.drawRect(3, dy + 3, block - 7, block - 7);//SH
				}
			}
			if ((!selected && inside && pressed)
					|| (selected && (!inside || !pressed))) {
				g.setColor(enabled ? c_text : c_disable);
				if (group == null) {
					g.fillRect(3, dy + block - 9, 2 + evm, 6 + evm);
					g.drawLine(3, dy + block - 4, block - 4, dy + 3);
					g.drawLine(4, dy + block - 4, block - 4, dy + 4);
				} else {
					g.fillRect(5, dy + 5, block - 10 + evm, block - 10 + evm);//SH
					g.drawRect(4, dy + 4, block - 9, block - 9);//SH
				}
			}
		} else if ("combobox" == classname) {
			if (getBoolean(component, "editable", true)) {
				Image icon = getIcon(component, "icon", null);
				int left = (icon != null) ? icon.getWidth() : 0;
				paintField(g, clipx, clipy, clipwidth, clipheight, component,
						bounds.width - block, bounds.height, focus, enabled,
						false, left);
				if (icon != null) {
					g.drawImage(icon, 2, bounds.height / 2, Graphics.VCENTER
							| Graphics.LEFT);
				}
				paintArrow(g, bounds.width - block, 0, block, bounds.height,
						'S', enabled, inside, pressed, "down", true, false,
						true, true, true);
			} else {
				paint(component, 0, 0, bounds.width, bounds.height, g, clipx,
						clipy, clipwidth, clipheight, true, true, true, true,
						1, 1, 1, 1 + block, focus,
						enabled ? ((inside != pressed) ? 'h' : (pressed ? 'p'
								: 'g')) : 'd', "left", false, false);
				g.setColor(enabled ? c_text : c_disable);
				paintArrow(g, bounds.width - block, 0, block, bounds.height,
						'S');
			}
		} else if (":combolist" == classname) {
			paintScroll(component, classname, pressed, inside, focus, enabled,
					g, clipx, clipy, clipwidth, clipheight);
		} else if (("textfield" == classname) || ("passwordfield" == classname)) {
			paintField(g, clipx, clipy, clipwidth, clipheight, component,
					bounds.width, bounds.height, focus, enabled,
					("passwordfield" == classname), 0);
		} else if ("textarea" == classname) {
			paintScroll(component, classname, pressed, inside, focus, enabled,
					g, clipx, clipy, clipwidth, clipheight);
		} else if ("tabbedpane" == classname) {
			int i = 0;
			Object selectedtab = null;
			int selected = getInteger(component, "selected", 0);
			String placement = getString(component, "placement", "top");
			boolean horizontal = ((placement == "top") || (placement == "bottom"));
			boolean stacked = (placement == "stacked");
			int bx = stacked ? 0 : horizontal ? 2 : 1, by = stacked ? 0
					: horizontal ? 1 : 2, bw = 2 * bx, bh = 2 * by;
			// paint tabs except the selected one
			g.clipRect(0, 0, bounds.width, bounds.height); //+clip
			for (Object tab = get(component, ":comp"); tab != null; tab = get(
					tab, ":next")) {
				Rectangle r = getRectangle(tab, "bounds");
				if (selected != i) {
					boolean hover = inside && (mousepressed == null)
							&& (insidepart == tab);
					boolean tabenabled = enabled
							&& getBoolean(tab, "enabled", true);
					paint(tab, r.x + bx, r.y + by, r.width - bw, r.height - bh,
							g, clipx, clipy, clipwidth, clipheight,
							(placement != "bottom"), (placement != "right"),
							!stacked && (placement != "top"),
							(placement != "left"), 1, 3, 1, 3, false,
							tabenabled ? (hover ? 'h' : 'g') : 'd', "left",
							true, false);

				} else {
					selectedtab = tab;
					// paint tabbedpane border
					paint(
							tab,
							(placement == "left") ? r.width - 1 : 0,
							stacked ? (r.y + r.height - 1)
									: (placement == "top") ? r.height - 1 : 0,
							(horizontal || stacked) ? bounds.width
									: (bounds.width - r.width + 1),
							stacked ? (bounds.height - r.y - r.height + 1)
									: horizontal ? (bounds.height - r.height + 1)
											: bounds.height, g, true, true,
							true, true, enabled ? 'e' : 'd');
					Object comp = get(selectedtab, ":comp");
					if ((comp != null) && getBoolean(comp, "visible", true)) {
						clipx -= r.x;
						clipy -= r.y;
						g.translate(r.x, r.y); // relative to tab
						paint(g, clipx, clipy, clipwidth, clipheight, comp,
								enabled);
						clipx += r.x;
						clipy += r.y;
						g.translate(-r.x, -r.y);
					}
				}
				i++;
			}

			// paint selected tab and its content
			if (selectedtab != null) {
				Rectangle r = getRectangle(selectedtab, "bounds");
				// paint selected tab
				int ph = stacked ? 3 : (horizontal ? 5 : 4);
				int pv = stacked ? 1 : (horizontal ? 2 : 3);
				paint(selectedtab, r.x, r.y, r.width, r.height, g, clipx,
						clipy, clipwidth, clipheight, (placement != "bottom"),
						(placement != "right"), !stacked
								&& (placement != "top"), (placement != "left"),
						pv, ph, pv, ph, focus, enabled ? 'b' : 'i', "left",
						true, false);
			}
			g.setClip(clipx, clipy, clipwidth, clipheight); //+clip
		} else if (("panel" == classname) || ("dialog" == classname)) {
			int titleheight = getInteger(component, ":titleheight", 0);
			if ("dialog" == classname) {
				paint(component, 0, 0, bounds.width, 3 + titleheight, g, clipx,
						clipy, clipwidth, clipheight, true, true, false, true,
						1, 2, 1, 2, false, 'g', "left", false, false);
				int controlx = bounds.width - titleheight - 1;
				// close dialog button
				if (get(component, "close") != null) {
					paint(component, g, controlx, 3, titleheight - 2,
							titleheight - 2, 'c',
							pressedpart == ":closebutton" ? 'p'
									: insidepart == ":closebutton" ? 'h' : 'g');
					controlx -= titleheight;
				}
				if (getBoolean(component, "maximizable", false)) {
					paint(component, g, controlx, 3, titleheight - 2,
							titleheight - 2, 'm', 'g');
					controlx -= titleheight;
				}
				if (getBoolean(component, "iconifiable", false)) {
					paint(component, g, controlx, 3, titleheight - 2,
							titleheight - 2, 'i', 'g');
				}
				paintRect(g, 0, 3 + titleheight, bounds.width, bounds.height
						- 3 - titleheight, c_border, c_press, false, true,
						true, true, true); // lower
				// part
				// excluding
				// titlebar
				paint(component, // content area
						3, 3 + titleheight, bounds.width - 6, bounds.height - 6
								- titleheight, g, true, true, true, true, 'b');
			} else { // panel
				boolean border = getBoolean(component, "border", false);
				paint(component, 0, titleheight / 2, bounds.width,
						bounds.height - (titleheight / 2), g, border, border,
						border, border, enabled ? 'e' : 'd');
				paint(component, 0, 0, bounds.width, titleheight, // panel title
						g, clipx, clipy, clipwidth, clipheight, false, false,
						false, false, 0, 3, 0, 3, false, enabled ? 'x' : 'd',
						"left", false, false);
			}

			if (get(component, ":port") != null) {
				paintScroll(component, classname, pressed, inside, focus,
						enabled, g, clipx, clipy, clipwidth, clipheight);
			} else {
				for (Object comp = get(component, ":comp"); comp != null; comp = get(
						comp, ":next")) {
					paint(g, clipx, clipy, clipwidth, clipheight, comp, enabled);
				}
			}
		} else if ("desktop" == classname) {
			paintRect(g, 0, 0, bounds.width, bounds.height, c_border, c_bg,
					false, false, false, false, true);
			paintReverse(g, clipx, clipy, clipwidth, clipheight, get(component,
					":comp"), enabled);
			//g.setColor(Color.red); if (clip != null) g.drawRect(clipx, clipy,
			// clipwidth, clipheight);
			if ((tooltipowner != null) && (component == content)) {
				Rectangle r = getRectangle(tooltipowner, ":tooltipbounds");
				paintRect(g, r.x, r.y, r.width, r.height, c_border, c_bg, true,
						true, true, true, true);
				String text = getString(tooltipowner, "tooltip", null);
				g.setColor(c_text);
				g.drawString(text, r.x + 2, r.y + (r.height - font.getHeight())
						/ 2, Graphics.TOP | Graphics.LEFT); //+nullpointerexception
			}
		} else if ("spinbox" == classname) {
			paintField(g, clipx, clipy, clipwidth, clipheight, component,
					bounds.width - block, bounds.height, focus, enabled, false,
					0);
			paintArrow(g, bounds.width - block, 0, block, bounds.height / 2,
					'N', enabled, inside, pressed, "up", true, false, false,
					true, true);
			paintArrow(g, bounds.width - block, bounds.height / 2, block,
					bounds.height - (bounds.height / 2), 'S', enabled, inside,
					pressed, "down", true, false, true, true, true);
		} else if ("progressbar" == classname) {
			int minimum = getInteger(component, "minimum", 0);
			int maximum = getInteger(component, "maximum", 100);
			int value = getInteger(component, "value", 0);
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int length = (value - minimum)
					* ((horizontal ? bounds.width : bounds.height) - 1)
					/ (maximum - minimum);
			paintRect(g, 0, 0, horizontal ? length : bounds.width,
					horizontal ? bounds.height : length, enabled ? c_border
							: c_disable, c_select, true, true, horizontal,
					!horizontal, true);
			paintRect(g, horizontal ? length : 0, horizontal ? 0 : length,
					horizontal ? (bounds.width - length) : bounds.width,
					horizontal ? bounds.height : (bounds.height - length),
					enabled ? c_border : c_disable, c_bg, true, true, true,
					true, true);
		} else if ("slider" == classname) {
			int minimum = getInteger(component, "minimum", 0);
			int maximum = getInteger(component, "maximum", 100);
			int value = getInteger(component, "value", 0);
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int length = (value - minimum)
					* ((horizontal ? bounds.width : bounds.height) - block)
					/ (maximum - minimum);
			paintRect(g, horizontal ? 0 : 3, horizontal ? 3 : 0,
					horizontal ? length : (bounds.width - 6),
					horizontal ? (bounds.height - 6) : length,
					enabled ? c_border : c_disable, c_bg, true, true,
					horizontal, !horizontal, true);
			paintRect(g, horizontal ? length : 0, horizontal ? 0 : length,
					horizontal ? block : bounds.width,
					horizontal ? bounds.height : block, enabled ? c_border
							: c_disable, enabled ? c_ctrl : c_bg, true, true,
					true, true, true);
			if (focus) {
				g.setColor(c_focus);
				g.drawRect(horizontal ? (length + 2) : 2, horizontal ? 2
						: (length + 2),
						(horizontal ? block : bounds.width) - 5,
						(horizontal ? bounds.height : block) - 5);
				//g.drawRect(length + 1, 1, block - 3, bounds.height - 3);
			}
			paintRect(g, horizontal ? (block + length) : 3, horizontal ? 3
					: (block + length), bounds.width
					- (horizontal ? (block + length) : 6), bounds.height
					- (horizontal ? 6 : (block + length)), enabled ? c_border
					: c_disable, c_bg, horizontal, !horizontal, true, true,
					true);
		} else if ("splitpane" == classname) {
			boolean horizontal = ("vertical" != get(component, "orientation"));
			int divider = getInteger(component, "divider", -1);
			paintRect(g, horizontal ? divider : 0, horizontal ? 0 : divider,
					horizontal ? 5 : bounds.width, horizontal ? bounds.height
							: 5, c_border, c_bg, false, false, false, false,
					true);
			g.setColor(enabled ? (focus ? c_focus : c_border) : c_disable);
			int xy = horizontal ? bounds.height : bounds.width;
			int xy1 = Math.max(0, xy / 2 - 12);
			int xy2 = Math.min(xy / 2 + 12, xy - 1);
			for (int i = divider + 1; i < divider + 4; i += 2) {
				if (horizontal) {
					g.drawLine(i, xy1, i, xy2);
				} else {
					g.drawLine(xy1, i, xy2, i);
				}
			}
			Object comp1 = get(component, ":comp");
			if (comp1 != null) {
				paint(g, clipx, clipy, clipwidth, clipheight, comp1, enabled);
				Object comp2 = get(comp1, ":next");
				if (comp2 != null) {
					paint(g, clipx, clipy, clipwidth, clipheight, comp2,
							enabled);
				}
			}
		} else if (("list" == classname) || ("table" == classname)
				|| ("tree" == classname)) {
			paintScroll(component, classname, pressed, inside, focus, enabled,
					g, clipx, clipy, clipwidth, clipheight);
		} else if ("separator" == classname) {
			g.setColor(enabled ? c_border : c_disable);
			g.fillRect(0, 0, bounds.width + evm, bounds.height + evm);
		} else if ("menubar" == classname) {
			Object selected = get(component, "selected");
			boolean bottom = ("bottom" == get(component, "placement"));
			int lastx = 0;
			for (Object menu = get(component, ":comp"); menu != null; menu = get(
					menu, ":next")) {
				Rectangle mb = getRectangle(menu, "bounds");
				if (clipx + clipwidth <= mb.x) {
					break;
				}
				if (clipx >= mb.x + mb.width) {
					continue;
				}
				boolean menuenabled = enabled
						&& getBoolean(menu, "enabled", true);
				boolean armed = (selected == menu);
				boolean hoover = (selected == null) && (insidepart == menu);
				paint(menu, mb.x, 0, mb.width, bounds.height, g, clipx, clipy,
						clipwidth, clipheight, // TODO
						// disabled
						bottom || armed, armed, !bottom || armed, armed, 1, 3,
						1, 3, false, enabled ? (menuenabled ? (armed ? 's'
								: (hoover ? 'h' : 'g')) : 'r') : 'd', "left",
						true, false);
				lastx = mb.x + mb.width;
			}
			paintRect(g, lastx, 0, bounds.width - lastx, bounds.height,
					enabled ? c_border : c_disable, enabled ? c_ctrl : c_bg,
					bottom, false, !bottom, false, true);
		} else if (":popup" == classname) {
			paintRect(g, 0, 0, bounds.width, bounds.height, c_border, c_textbg,
					true, true, true, true, true);
			Object selected = get(component, "selected");
			for (Object menu = get(get(component, "menu"), ":comp"); menu != null; menu = get(
					menu, ":next")) {
				Rectangle r = getRectangle(menu, "bounds");
				if (clipy + clipheight <= r.y) {
					break;
				}
				if (clipy >= r.y + r.height) {
					continue;
				}
				String itemclass = getClass(menu);
				if (itemclass == "separator") {
					g.setColor(c_border);
					g
							.fillRect(r.x, r.y, bounds.width - 2 + evm,
									r.height + evm);
				} else {
					boolean armed = (selected == menu);
					boolean menuenabled = getBoolean(menu, "enabled", true);
					paint(
							menu,
							r.x,
							r.y,
							bounds.width - 2,
							r.height,
							g,
							clipx,
							clipy,
							clipwidth,
							clipheight,
							false,
							false,
							false,
							false,
							2,
							(itemclass == "checkboxmenuitem") ? (block + 7) : 4,
							2, 4, false, menuenabled ? (armed ? 's' : 't')
									: 'd', "left", true, false);
					if (itemclass == "checkboxmenuitem") {
						boolean checked = getBoolean(menu, "selected", false);
						String group = getString(menu, "group", null);
						g.translate(r.x + 4, r.y + 2);
						g.setColor(menuenabled ? c_border : c_disable);
						if (group == null) {
							g.drawRect(1, 1, block - 3, block - 3);
						} else {
							//g.drawOval(1, 1, block - 3, block - 3);
						}
						if (checked) {
							g.setColor(menuenabled ? c_text : c_disable);
							if (group == null) {
								g.fillRect(3, block - 9, 2 + evm, 6 + evm);
								g.drawLine(3, block - 4, block - 4, 3);
								g.drawLine(4, block - 4, block - 4, 4);
							} else {
								//g.fillOval(5, 5, block - 10 + evm, block - 10
								// + evm);
								//g.drawOval(4, 4, block - 9, block - 9);
							}
						}
						g.translate(-r.x - 4, -r.y - 2);
					}
					if (itemclass == "menu") {
						paintArrow(g, r.x + bounds.width - block, r.y, block,
								r.height, 'E');
					} else {
						String accelerator = getAccelerator(menu);
						if (accelerator != null) { //TODO
							g.drawString(accelerator, bounds.width - 4
									- font.stringWidth(accelerator),
									r.y + 2 + 10, Graphics.TOP | Graphics.LEFT);
						}
					}
				}
			}
		} else
			throw new IllegalArgumentException(classname);
		g.translate(-bounds.x, -bounds.y);
		clipx += bounds.x;
		clipy += bounds.y;
	}

	/**
	 * Paints the dialog close, maximize and iconify buttons
	 */
	private void paint(Object component, Graphics g, int x, int y, int width,
			int height, char type, char mode) {
		paint(component, x, y, width, height, g, true, true, true, true, mode);
		g.setColor(c_text);
		switch (type) {
		case 'c': // closable dialog button
			g.drawLine(x + 3, y + 4, x + width - 5, y + height - 4);
			g.drawLine(x + 3, y + 3, x + width - 4, y + height - 4);
			g.drawLine(x + 4, y + 3, x + width - 4, y + height - 5);
			g.drawLine(x + width - 5, y + 3, x + 3, y + height - 5);
			g.drawLine(x + width - 4, y + 3, x + 3, y + height - 4);
			g.drawLine(x + width - 4, y + 4, x + 4, y + height - 4);
			break;
		case 'm': // maximizable dialog button
			g.drawRect(x + 3, y + 3, width - 7, height - 7);
			g.drawLine(x + 4, y + 4, x + width - 5, y + 4);
			break;
		case 'i': // iconifiable dialog button
			g.fillRect(x + 3, y + height - 5, width - 6, 2);
			break;
		}
	}

	/**
	 * Paint component's borders and background
	 */
	private void paint(Object component, int x, int y, int width, int height,
			Graphics g, boolean top, boolean left, boolean bottom,
			boolean right, char mode) {
		if ((width <= 0) || (height <= 0)) {
			return;
		}

		if (top || left || bottom || right) { // draw border
			g.setColor(((mode != 'd') && (mode != 'i')) ? c_border : c_disable);
			if (top) {
				g.drawLine(x + width - 1, y, x, y);
				y++;
				height--;
				if (height <= 0) {
					return;
				}
			}
			if (left) {
				g.drawLine(x, y, x, y + height - 1);
				x++;
				width--;
				if (width <= 0) {
					return;
				}
			}
			if (bottom) {
				g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
				height--;
				if (height <= 0) {
					return;
				}
			}
			if (right) {
				g.drawLine(x + width - 1, y + height - 1, x + width - 1, y);
				width--;
				if (width <= 0) {
					return;
				}
			}
		}

		Integer Bg = (Integer) get(component, "background");
		int background = (Bg != null) ? Bg.intValue() : c_bg;
		switch (mode) {
		case 'e':
		case 'l':
		case 'd':
		case 'g':
		case 'r':
			break;
		case 'b':
		case 'i':
		case 'x':
			if (Bg == null) {
				background = c_bg;
			}
			break;
		case 'h':
			background = c_hover;
			break;
		case 'p':
			background = c_press;
			break;
		case 't':
			if (Bg == null) {
				background = c_textbg;
			}
			break;
		case 's':
			background = c_select;
			break;
		default:
			throw new IllegalArgumentException();
		}
		if (((mode == 'g') || (mode == 'r')) && (Bg == null)) {
			fill(g, x, y, width, height, true);
		} else if (Bg != null) {
			g.setColor(background);
			if (mode != 'x') {
				g.fillRect(x, y, width + evm, height + evm);
			}
		}
	}

	/**
	 * Paint component icon and text (using default or custom font)
	 * 
	 * @param mnemonic
	 *            find mnemonic index and underline text
	 */
	private void paint(Object component, int x, int y, int width, int height,
			Graphics g, int clipx, int clipy, int clipwidth, int clipheight,
			boolean top, boolean left, boolean bottom, boolean right,
			int toppadding, int leftpadding, int bottompadding,
			int rightpadding, boolean focus, char mode, String alignment,
			boolean mnemonic, boolean underline) {
		paint(component, x, y, width, height, g, top, left, bottom, right, mode);
		if (top) {
			y++;
			height--;
		}
		if (left) {
			x++;
			width--;
		}
		if (bottom) {
			height--;
		}
		if (right) {
			width--;
		}
		if ((width <= 0) || (height <= 0)) {
			return;
		}

		if (focus) {
			g.setColor(c_focus);
			g.drawRect(x + 1, y + 1, width - 3, height - 3);
		}

		String text = getString(component, "text", null);
		Image icon = getIcon(component, "icon", null);
		if ((text == null) && (icon == null)) {
			return;
		}

		x += leftpadding;
		y += toppadding;
		width -= leftpadding + rightpadding;
		height -= toppadding + bottompadding;

		alignment = getString(component, "alignment", alignment);
		//SH
		//Font customfont = (text != null) ? (Font) get(component, "font") :
		// null;
		Font customfont = font;
		if (customfont != null) {
			g.setFont(customfont);
		} else
			customfont = g.getFont();

		int tw = 0, th = 0;
		int ta = 0;
		if (text != null) {
			tw = customfont.stringWidth(text);
			ta = customfont.getBaselinePosition();
			th = customfont.getHeight();
		}
		int iw = 0, ih = 0;
		if (icon != null) {
			iw = icon.getWidth();
			ih = icon.getHeight();
			if (text != null) {
				iw += 2;
			}
		}

		boolean clipped = (tw + iw > width) || (th > height) || (ih > height);
		int cx = x;
		if ("center" == alignment) {
			cx += (width - tw - iw) / 2;
		} else if ("right" == alignment) {
			cx += width - tw - iw;
		}

		if (clipped) {
			g.clipRect(x, y, width, height);
		}
		if (mode == 'x') {
			g.drawLine(cx, y + height / 2, cx + iw + tw, y + height / 2);
		}
		if (icon != null) {
			g.drawImage(icon, cx, y + (height - ih) / 2, Graphics.TOP
					| Graphics.LEFT);
			cx += iw;
		}
		if (text != null) {
			Integer Fg = (Integer) get(component, "foreground");
			int foreground = (Fg != null) ? Fg.intValue() : c_text;
			if (Fg == null) {
				foreground = (mode == 'l') ? 0x0000ff
						: (((mode != 'd') && (mode != 'r')) ? c_text
								: c_disable);
			}
			g.setColor(foreground);
			int ty = y + (height - th) / 2 + ta;
			g.drawString(text, cx, y + (height - th) / 2, Graphics.TOP
					| Graphics.LEFT);
			if (mnemonic) {
				int imnemonic = getInteger(component, "mnemonic", -1);
				if ((imnemonic != -1) && (imnemonic < text.length())) {
					int mx = cx
							+ customfont.stringWidth(text.substring(0,
									imnemonic));
					ty = (height + th) / 2;
					g.drawLine(mx, ty + 1, mx
							+ customfont.charWidth(text.charAt(imnemonic)),
							ty + 1);
				}
			}
			if (underline) { // for link button
				g.drawLine(cx, ty + 1, cx + tw, ty + 1);
			}
		}
		if (clipped) {
			g.setClip(clipx, clipy, clipwidth, clipheight);
		}

		if (customfont != null) {
			g.setFont(font);
		} //restore the default font
	}

	/**
	 * Paint scrollable content
	 * 
	 * @param component
	 *            a panel
	 */
	private void paint(Object component, String classname, boolean focus,
			boolean enabled, Graphics g, int clipx, int clipy, int clipwidth,
			int clipheight, int portwidth, int viewwidth) {
		if ("textarea" == classname) {
			char[] chars = (char[]) get(component, ":text");
			int start = focus ? getInteger(component, "start", 0) : 0;
			int end = focus ? getInteger(component, "end", 0) : 0;
			int is = Math.min(start, end);
			int ie = Math.max(start, end);
			//SH
			//Font customfont = (Font) get(component, "font");
			Font customfont = font;
			if (customfont != null) {
				g.setFont(customfont);
			} else
				customfont = g.getFont();
			int fontascent = customfont.getBaselinePosition();
			int fontheight = customfont.getHeight();
			int ascent = 1;

			for (int i = 0, j = 0; j <= chars.length; j++) {
				if ((j == chars.length) || (chars[j] == '\n')) {
					if (clipy + clipheight <= ascent) {
						break;
					} // the next lines are bellow paint rectangle
					if (clipy < ascent + fontheight) { // this line is not above
						// painting
						// area
						if (focus && (is != ie) && (ie >= i) && (is <= j)) {
							int xs = (is < i) ? -1
									: ((is > j) ? (viewwidth - 1) : customfont
											.charsWidth(chars, i, is - i));
							int xe = ((j != -1) && (ie > j)) ? (viewwidth - 1)
									: customfont.charsWidth(chars, i, ie - i);
							g.setColor(c_select);
							g.fillRect(1 + xs, ascent, xe - xs + evm,
									fontheight + evm);
						}
						g.setColor(enabled ? c_text : c_disable);
						g.drawChars(chars, i, j - i, 1, ascent, Graphics.TOP
								| Graphics.LEFT);
						if (focus && (end >= i) && (end <= j)) {
							int caret = customfont
									.charsWidth(chars, i, end - i);
							g.setColor(c_focus);
							g
									.fillRect(caret, ascent, 1 + evm,
											fontheight + evm);
						}
					}
					ascent += fontheight;
					i = j + 1;
				}
			}
			if (customfont != null) {
				g.setFont(font);
			} //restore the default font
		} else if (":combolist" == classname) {
			Object lead = get(component, ":lead");
			for (Object choice = get(get(component, "combobox"), ":comp"); choice != null; choice = get(
					choice, ":next")) {
				Rectangle r = getRectangle(choice, "bounds");
				if (clipy + clipheight <= r.y) {
					break;
				}
				if (clipy >= r.y + r.height) {
					continue;
				}
				paint(
						choice,
						r.x,
						r.y,
						portwidth,
						r.height,
						g,
						clipx,
						clipy,
						clipwidth,
						clipheight,
						false,
						false,
						false,
						false,
						2,
						4,
						2,
						4,
						false,
						getBoolean(choice, "enabled", true) ? ((lead == choice) ? 's'
								: 't')
								: 'd', "left", false, false);
			}
		} else if (("panel" == classname) || ("dialog" == classname)) {
			for (Object comp = get(component, ":comp"); comp != null; comp = get(
					comp, ":next")) {
				paint(g, clipx, clipy, clipwidth, clipheight, comp, enabled);
			}
		} else { //if (("list" == classname) || ("table" == classname) ||
			// ("tree"
			// == classname))
			Object lead = get(component, ":lead");
			if (getIndex(component, lead) == -1) {
				set(component, ":lead", lead = null);
			}
			int[] columnwidths = ("table" == classname) ? ((int[]) get(
					component, ":widths")) : null;
			boolean line = getBoolean(component, "line", true);
			int iline = line ? 1 : 0;
			boolean angle = ("tree" == classname)
					&& getBoolean(component, "angle", false);
			for (Object item = get(component, ":comp"), next = null; item != null; item = next) {
				if (focus && (lead == null)) {
					set(component, ":lead", lead = item); // draw first item
					// focused when
					// lead is null
				}
				Rectangle r = getRectangle(item, "bounds");
				if (clipy + clipheight <= r.y) {
					break;
				} // clip rectangle is above
				boolean subnode = false;
				boolean expanded = false;
				if ("tree" != classname) {
					next = get(item, ":next");
				} else {
					subnode = (next = get(item, ":comp")) != null;
					expanded = subnode && getBoolean(item, "expanded", true);
					if (!expanded) {
						for (Object node = item; (node != component)
								&& ((next = get(node, ":next")) == null); node = getParent(node))
							;
					}
				}
				if (clipy >= r.y + r.height + iline) {
					if (angle) {
						Object nodebelow = get(item, ":next");
						if (nodebelow != null) { // and the next node is bellow
							// clipy
							g.setColor(c_bg);
							int x = r.x - block / 2;
							g.drawLine(x, r.y, x, getRectangle(nodebelow,
									"bounds").y);
						}
					}
					continue; // clip rectangle is bellow
				}

				boolean selected = getBoolean(item, "selected", false);
				boolean focused = focus && (lead == item);
				paintRect(g, ("tree" != classname) ? 0 : r.x, r.y,
						("tree" != classname) ? viewwidth : r.width, r.height,
						c_focus, selected ? c_select : c_textbg, focused,
						focused, focused, focused, true);
				if (line) {
					g.setColor(c_bg);
					g.drawLine(0, r.y + r.height, viewwidth, r.y + r.height);
				}
				if ("table" != classname) { // list or tree
					boolean itemenabled = enabled
							&& getBoolean(item, "enabled", true);
					paint(item, r.x, r.y, viewwidth, r.height, g, clipx, clipy,
							clipwidth, clipheight, false, false, false, false,
							1, 3, 1, 3, false, itemenabled ? 'e' : 'd', "left",
							false, false);
					if ("tree" == classname) {
						int x = r.x - block / 2;
						int y = r.y + (r.height - 1) / 2;
						if (angle) {
							g.setColor(c_bg);
							g.drawLine(x, r.y, x, y);
							g.drawLine(x, y, r.x - 1, y);
							Object nodebelow = get(item, ":next");
							if (nodebelow != null) {
								g.drawLine(x, y, x, getRectangle(nodebelow,
										"bounds").y);
							}
						}
						if (subnode) {
							paintRect(g, x - 4, y - 4, 9, 9,
									itemenabled ? c_border : c_disable,
									itemenabled ? c_ctrl : c_bg, true, true,
									true, true, true);
							g.setColor(itemenabled ? c_text : c_disable);
							g.drawLine(x - 2, y, x + 2, y);
							if (!expanded) {
								g.drawLine(x, y - 2, x, y + 2);
							}
						}
					}
				} else { // table
					int i = 0;
					int x = 0;
					for (Object cell = get(item, ":comp"); cell != null; cell = get(
							cell, ":next")) {
						if (clipx + clipwidth <= x) {
							break;
						}
						//column width is defined by header calculated in
						// layout, otherwise
						// is 80
						int iwidth = 80;
						if ((columnwidths != null) && (columnwidths.length > i)) {
							iwidth = (i != columnwidths.length - 1) ? columnwidths[i]
									: Math.max(iwidth, viewwidth - x);
						}
						if (clipx < x + iwidth) {
							boolean cellenabled = enabled
									&& getBoolean(cell, "enabled", true);
							paint(cell, r.x + x, r.y, iwidth, r.height - 1, g,
									clipx, clipy, clipwidth, clipheight, false,
									false, false, false, 1, 1, 1, 1, false,
									cellenabled ? 'e' : 'd', "left", false,
									false);
						}
						i++;
						x += iwidth;
					}
				}
			}
		}
	}

	/**
	 *  
	 */
	private void paintArrow(Graphics g, int x, int y, int width, int height,
			char dir) {
		int cx = x + width / 2 - 2;
		int cy = y + height / 2 - 2;
		for (int i = 0; i < 4; i++) {
			if (dir == 'N') { // north
				g.drawLine(cx + 1 - i, cy + i, cx + 1/* 2 */+ i, cy + i);
			} else if (dir == 'W') { // west
				g.drawLine(cx + i, cy + 1 - i, cx + i, cy + 1/* 2 */+ i);
			} else if (dir == 'S') { // south
				g.drawLine(cx + 1 - i, cy + 4 - i, cx + 1/* 2 */+ i, cy + 4
						- i);
			} else { // east
				g.drawLine(cx + 4 - i, cy + 1 - i, cx + 4 - i, cy + 1/* 2 */
						+ i);
			}
		}
	}

	/**
	 *  
	 */
	private void paintArrow(Graphics g, int x, int y, int width, int height,
			char dir, boolean enabled, boolean inside, boolean pressed,
			String part, boolean top, boolean left, boolean bottom,
			boolean right, boolean horizontal) {
		inside = inside && (insidepart == part);
		pressed = pressed && (pressedpart == part);
		paintRect(g, x, y, width, height, enabled ? c_border : c_disable,
				enabled ? ((inside != pressed) ? c_hover : (pressed ? c_press
						: c_ctrl)) : c_bg, top, left, bottom, right, horizontal);
		g.setColor(enabled ? c_text : c_disable);
		paintArrow(g, x + (left ? 1 : 0), y + (top ? 1 : 0), width
				- (left ? 1 : 0) - (right ? 1 : 0), height - (top ? 1 : 0)
				- (bottom ? 1 : 0), dir);
	}

	/**
	 *  
	 */
	private void paintField(Graphics g, int clipx, int clipy, int clipwidth,
			int clipheight, Object component, int width, int height,
			boolean focus, boolean enabled, boolean hidden, int left) {

		boolean editable = getBoolean(component, "editable", true);
		paintRect(g, 0, 0, width, height, enabled ? c_border : c_disable,
				editable ? c_textbg : c_bg, true, true, true, true, true);
		g.clipRect(1 + left, 1, width - left - 2, height - 2);

		String text = getString(component, "text", "");
		int offset = getInteger(component, ":offset", 0);
		//SH
		//Font currentfont = (Font) get(component, "font");
		Font currentfont = font;
		if (currentfont != null) {
			g.setFont(currentfont);
		} else
			currentfont = g.getFont();

		int caret = 0;
		int start = getInteger(component, "start", 0);
		int end = getInteger(component, "end", 0);
		if (focus) {
			caret = hidden ? (currentfont.charWidth('*') * end) : currentfont
					.stringWidth(text.substring(0, end));
		}

		int fx = 2 + left - offset;
		// XXXX int fy = (height + fm.getAscent() - fm.getDescent()) / 2;
		int fy = (height - currentfont.getHeight()) / 2;

		//
		// Support alignment for textfields
		//
		int cursx = 1 + left - offset + caret;
		String alignment = getChoice(component, "alignment");
		if ("center" == alignment) {
			int cx = (width - currentfont.stringWidth(text)) / 2 - 2;
			if (cx > 0) {
				cursx += cx - fx;
				fx = cx;
			}
		} else if ("right" == alignment) {
			int cx = width - currentfont.stringWidth(text) - 2;
			if (cx > 0) {
				cursx += cx - fx;
				fx = cx;
			}
		}

		if (focus) {
			if (start != end) {
				int is = hidden ? (currentfont.charWidth('*') * start)
						: currentfont.stringWidth(text.substring(0, start));
				g.setColor(c_select);
				g.fillRect(fx + Math.min(is, caret), 1, Math.abs(caret - is)
						+ evm, height - 2 + evm);
			}

			g.setColor(c_focus);
			g.fillRect(cursx, 1, 2 + evm, height - 2 + evm); // DAL - alignment
			// cursx
			// and fatter cursor
		}
		g.setColor(enabled ? c_text : c_disable);

		if (hidden) {
			int fh = currentfont.charWidth('*');
			for (int i = text.length(); i > 0; i--) {
				g.drawString("*", fx, fy, Graphics.TOP | Graphics.LEFT);
				fx += fh;
			}
		} else {
			g.drawString(text, fx, fy, Graphics.TOP | Graphics.LEFT);
		}
		if (currentfont != null) {
			g.setFont(font);
		}
		g.setClip(clipx, clipy, clipwidth, clipheight);
	}

	/**
	 *  
	 */
	private void paintRect(Graphics g, int x, int y, int width, int height,
			int border, int bg, boolean top, boolean left, boolean bottom,
			boolean right, boolean horizontal) {

		if ((width <= 0) || (height <= 0))
			return;
		g.setColor(border);
		if (top) {
			g.drawLine(x + width - 1, y, x, y);
			y++;
			height--;
			if (height <= 0)
				return;
		}
		if (left) {
			g.drawLine(x, y, x, y + height - 1);
			x++;
			width--;
			if (width <= 0)
				return;
		}
		if (bottom) {
			g.drawLine(x, y + height - 1, x + width - 1, y + height - 1);
			height--;
			if (height <= 0)
				return;
		}
		if (right) {
			g.drawLine(x + width - 1, y + height - 1, x + width - 1, y);
			width--;
			if (width <= 0)
				return;
		}

		if (bg == c_ctrl) {
			fill(g, x, y, width, height, horizontal);
		} else {
			g.setColor(bg);
			g.fillRect(x, y, width + evm, height + evm);
		}
	}

	/**
	 *  
	 */
	private void paintReverse(Graphics g, int clipx, int clipy, int clipwidth,
			int clipheight, Object component, boolean enabled) {
		if (component != null) {
			Rectangle bounds = getRectangle(component, "bounds");
			if ((clipx < bounds.x)
					|| (clipx + clipwidth > bounds.x + bounds.width)
					|| (clipy < bounds.y)
					|| (clipy + clipheight > bounds.y + bounds.height)) {
				paintReverse(g, clipx, clipy, clipwidth, clipheight, get(
						component, ":next"), enabled);
			}
			paint(g, clipx, clipy, clipwidth, clipheight, component, enabled);
		}
	}

	/**
	 * @param component
	 *            scrollable widget
	 * @param classname
	 * @param pressed
	 * @param inside
	 * @param focus
	 * @param enabled
	 * @param g
	 *            grahics context
	 * @param clipx
	 *            current cliping x location relative to the component
	 * @param clipy
	 *            y location of the cliping area relative to the component
	 * @param clipwidth
	 *            width of the cliping area
	 * @param clipheight
	 *            height of the cliping area
	 */
	private void paintScroll(Object component, String classname,
			boolean pressed, boolean inside, boolean focus, boolean enabled,
			Graphics g, int clipx, int clipy, int clipwidth, int clipheight) {
		Rectangle port = getRectangle(component, ":port");
		Rectangle horizontal = getRectangle(component, ":horizontal");
		Rectangle vertical = getRectangle(component, ":vertical");
		Rectangle view = getRectangle(component, ":view");
		if (horizontal != null) { // paint horizontal scrollbar
			int x = horizontal.x;
			int y = horizontal.y;
			int width = horizontal.width;
			int height = horizontal.height;
			paintArrow(g, x, y, block, height, 'W', enabled, inside, pressed,
					"left", true, true, true, false, true);
			paintArrow(g, x + width - block, y, block, height, 'E', enabled,
					inside, pressed, "right", true, false, true, true, true);

			int track = width - (2 * block);
			if (track < 10) {
				paintRect(g, x + block, y, track, height, enabled ? c_border
						: c_disable, c_bg, true, true, true, true, true);
			} else {
				int knob = Math.max(track * port.width / view.width, 10);
				int decrease = view.x * (track - knob)
						/ (view.width - port.width);
				paintRect(g, x + block, y, decrease, height, enabled ? c_border
						: c_disable, c_bg, false, true, true, false, true);
				paintRect(g, x + block + decrease, y, knob, height,
						enabled ? c_border : c_disable,
						enabled ? c_ctrl : c_bg, true, true, true, true, true);
				int n = Math.min(5, (knob - 4) / 3);
				g.setColor(enabled ? c_border : c_disable);
				int cx = (x + block + decrease) + (knob + 2 - n * 3) / 2;
				for (int i = 0; i < n; i++) {
					g.drawLine(cx + i * 3, y + 3, cx + i * 3, y + height - 5);
				}
				int increase = track - decrease - knob;
				paintRect(g, x + block + decrease + knob, y, increase, height,
						enabled ? c_border : c_disable, c_bg, false, false,
						true, true, true);
			}
		}

		if (vertical != null) { // paint vertical scrollbar
			int x = vertical.x;
			int y = vertical.y;
			int width = vertical.width;
			int height = vertical.height;
			paintArrow(g, x, y, width, block, 'N', enabled, inside, pressed,
					"up", true, true, false, true, false);
			paintArrow(g, x, y + height - block, width, block, 'S', enabled,
					inside, pressed, "down", false, true, true, true, false);

			int track = height - (2 * block);
			if (track < 10) {
				paintRect(g, x, y + block, width, track, enabled ? c_border
						: c_disable, c_bg, true, true, true, true, false);
			} else {
				int knob = Math.max(track * port.height / view.height, 10);
				int decrease = view.y * (track - knob)
						/ (view.height - port.height);
				paintRect(g, x, y + block, width, decrease, enabled ? c_border
						: c_disable, c_bg, true, false, false, true, false);
				paintRect(g, x, y + block + decrease, width, knob,
						enabled ? c_border : c_disable,
						enabled ? c_ctrl : c_bg, true, true, true, true, false);
				int n = Math.min(5, (knob - 4) / 3);
				g.setColor(enabled ? c_border : c_disable);
				int cy = (y + block + decrease) + (knob + 2 - n * 3) / 2;
				for (int i = 0; i < n; i++) {
					g.drawLine(x + 3, cy + i * 3, x + width - 5, cy + i * 3);
				}
				int increase = track - decrease - knob;
				paintRect(g, x, y + block + decrease + knob, width, increase,
						enabled ? c_border : c_disable, c_bg, false, false,
						true, true, false);
			}
		}

		boolean hneed = (horizontal != null);
		boolean vneed = (vertical != null);
		if (("panel" != classname)
				&& ("dialog" != classname)
				&& (("textarea" != classname) || getBoolean(component,
						"border", true))) {
			paintRect(g, port.x - 1, port.y - 1, port.width + (vneed ? 1 : 2),
					port.height + (hneed ? 1 : 2), enabled ? c_border
							: c_disable, c_textbg, true, true, !hneed, !vneed,
					true);
			if ("table" == classname) {
				Object header = get(component, "header");
				if (header != null) {
					int[] columnwidths = (int[]) get(component, ":widths");
					Object column = get(header, ":comp");
					int x = 0;
					g.clipRect(0, 0, port.width + 2, port.y); // not 2 and
					// decrease clip
					// area...
					for (int i = 0; i < columnwidths.length; i++) {
						if (i != 0) {
							column = get(column, ":next");
						}
						boolean lastcolumn = (i == columnwidths.length - 1);
						int width = lastcolumn ? (view.width - x + 2)
								: columnwidths[i];
						//pressed (p), hovered (h), selected (b), default (g)
						// or disabled
						// (d)
						paint(
								column,
								x - view.x,
								0,
								width,
								port.y - 1,
								g,
								clipx,
								clipy,
								clipwidth,
								clipheight,
								true,
								true,
								false,
								lastcolumn,
								1,
								1,
								0,
								0,
								false,
								enabled ? (pressedpart == column ? 'p'
										: (insidepart == column ? 'h'
												: (getBoolean(column,
														"selected") ? 'b' : 'g')))
										: 'd', "left", false, false);

						Object sort = get(column, "sort"); // "none", "ascent",
						// "descent"
						if (sort != null && sort != "none") {
							paintArrow(g, x - view.x + width - block, 0, block,
									port.y, (sort == "ascent") ? 'S' : 'N');
						}
						x += width;
					}
					g.setClip(clipx, clipy, clipwidth, clipheight);
				}
			}
		}
		int x1 = Math.max(clipx, port.x);
		int x2 = Math.min(clipx + clipwidth, port.x + port.width);
		int y1 = Math.max(clipy, port.y);
		int y2 = Math.min(clipy + clipheight, port.y + port.height);
		if ((x2 > x1) && (y2 > y1)) {
			g.clipRect(x1, y1, x2 - x1, y2 - y1);
			g.translate(port.x - view.x, port.y - view.y);

			paint(component, classname, focus, enabled, g,
					view.x - port.x + x1, view.y - port.y + y1, x2 - x1, y2
							- y1, port.width, view.width);

			g.translate(view.x - port.x, view.y - port.y);
			g.setClip(clipx, clipy, clipwidth, clipheight);
		}
	}

	/**
	 * Creates a component from the given stream
	 * 
	 * @param inputstream
	 *            e.g. <i>new URL("http://myserver/myservlet").openStream() </i>
	 * @return the root component of the parsed stream
	 * @throws java.io.IOException
	 */
	public Object parse(InputStream inputstream) throws IOException {
		return parse(inputstream, this);
	}

	/**
	 * 
	 * @param inputstream
	 * @param validate
	 *            parse GUI from xml if true
	 * @param dom
	 *            parse an xml resoource
	 * @param handler
	 * @return
	 * @throws java.io.IOException
	 * @throws java.lang.IllegalArgumentException
	 */
	private Object parse(InputStream inputstream, boolean validate,
			boolean dom, Object handler) throws IOException {
		Reader reader = new InputStreamReader(inputstream);
		try {
			Object[] parentlist = null;
			Object current = null;
			Hashtable attributelist = null;
			Vector methods = (validate && !dom) ? new Vector() : null;
			StringBuffer text = new StringBuffer();
			for (int c = reader.read(); c != -1;) {
				if (c == '<') {
					if ((c = reader.read()) == '/') { //endtag
						if (text.length() > 0) {
							if (text.charAt(text.length() - 1) == ' ') {
								text.setLength(text.length() - 1);
							}
							if (!validate) {
								if (dom) {
									set(current, ":text", text.toString());
								} else {
									characters(text.toString());
								}
							}
							// else {
							//addContent(current, text.toString());
							//}
							text.setLength(0);
						}
						String tagname = (String) parentlist[2]; //getClass(current);
						for (int i = 0; i < tagname.length(); i++) { // current-tag
							if ((c = reader.read()) != tagname.charAt(i)) {
								throw new IllegalArgumentException(tagname);
							}
						}
						while (" \t\n\r".indexOf(c = reader.read()) != -1)
							; // whitespace
						if (c != '>')
							throw new IllegalArgumentException(); // '>'
						c = reader.read();
						if (!validate && !dom) {
							endElement();
						}
						if (parentlist[0] == null) {
							reader.close();
							finishParse(methods, current, handler);
							return current;
						}
						current = parentlist[0];
						parentlist = (Object[]) parentlist[1];
					} else if (c == '!') { // DOCTYPE
						while ((c = reader.read()) != '>')
							; //+(-1)
					} else if (c == '?') { // Processing Instructions
						boolean question = false; // read until '?>'
						while (((c = reader.read()) != '>') || !question) {
							question = (c == '?');
						}
					} else { //start or standalone tag
						text.setLength(0);
						boolean iscomment = false;
						while (">/ \t\n\r".indexOf(c) == -1) {
							text.append((char) c);
							if ((text.length() == 3) && (text.charAt(0) == '!')
									&& (text.charAt(1) == '-')
									&& (text.charAt(2) == '-')) {
								int m = 0;
								while (true) {
									c = reader.read();
									if (c == '-') {
										m++;
									} else if ((c == '>') && (m >= 2)) {
										break;
									} else {
										m = 0;
									}
								}
								iscomment = true;
							}
							c = reader.read();
						}
						if (iscomment) {
							continue;
						}
						String tagname = text.toString();
	
						parentlist = new Object[] { current, parentlist,
								tagname };
						if (validate) {
							current = (current != null) ? addElement(current,
									tagname) : create(tagname);
						} else {
							if (dom) {
								Object parent = current;
								//current = createImpl(tagname = tagname);
								current = createImpl(tagname);
								if (parent != null) {
									insertItem(parent, tagname, current, -1);
									//set(current, ":parent", parent);
								}
							} else {
								current = tagname;
							}
						}
						text.setLength(0);
						while (true) {
							boolean whitespace = false;
							while (" \t\n\r".indexOf(c) != -1) {
								c = reader.read();
								whitespace = true;
							}
							if (c == '>') {
								if (!validate && !dom) {
									startElement((String) current,
											attributelist);
									attributelist = null;
								}
								c = reader.read();
								break;
							} else if (c == '/') {
								if ((c = reader.read()) != '>') {
									throw new IllegalArgumentException(); // '>'
								}
								if (!validate && !dom) {
									startElement((String) current,
											attributelist);
									attributelist = null;
									endElement();
								}
								if (parentlist[0] == null) {
									reader.close();
									finishParse(methods, current, handler);
									return current;
								}
								current = parentlist[0];
								parentlist = (Object[]) parentlist[1];
								c = reader.read();
								break;
							} else if (whitespace) {
								while ("= \t\n\r".indexOf(c) == -1) {
									text.append((char) c);
									c = reader.read();
								}
								String key = text.toString();
								text.setLength(0);
								while (" \t\n\r".indexOf(c) != -1)
									c = reader.read();
								if (c != '=')
									throw new IllegalArgumentException();
								while (" \t\n\r".indexOf(c = reader.read()) != -1)
									;
								char quote = (char) c;
								if ((c != '\"') && (c != '\''))
									throw new IllegalArgumentException();
								while (quote != (c = reader.read())) {
									if (c == '&') {
										StringBuffer eb = new StringBuffer();
										while (';' != (c = reader.read())) {
											eb.append((char) c);
										}
										String entity = eb.toString();
										if ("lt".equals(entity)) {
											text.append('<');
										} else if ("gt".equals(entity)) {
											text.append('>');
										} else if ("amp".equals(entity)) {
											text.append('&');
										} else if ("quot".equals(entity)) {
											text.append('"');
										} else if ("apos".equals(entity)) {
											text.append('\'');
										} else if (entity.startsWith("#")) {
											boolean hexa = (entity.charAt(1) == 'x');
											text.append((char) Integer
													.parseInt(entity
															.substring(hexa ? 2
																	: 1),
															hexa ? 16 : 10));
										} else
											throw new IllegalArgumentException(
													"unknown " + "entity "
															+ entity);
									} else
										text.append((char) c);
								}
								if (validate) {
									addAttribute(current, key, text.toString(),
											methods);
								} else {
									if (dom) {
										set(current, key, text.toString());
									} else {
										if (attributelist == null) {
											attributelist = new Hashtable();
										}
										attributelist.put(key, text.toString());
									}
								}
								//'<![CDATA[' ']]>'
								text.setLength(0);
								c = reader.read();
							} else
								throw new IllegalArgumentException();
						}
					}
				} else {
					if (" \t\n\r".indexOf(c) != -1) {
						if ((text.length() > 0)
								&& (text.charAt(text.length() - 1) != ' ')) {
							text.append(' ');
						}
					} else {
						text.append((char) c);
					}
					c = reader.read();
				}
			}
			throw new IllegalArgumentException();
		} finally {
			if (reader != null) {
				reader.close();
			}
		}
	}

	/**
	 * Creates a component from the given stream and event handler
	 * 
	 * @param inputstream
	 *            read xml from this stream
	 * @param handler
	 *            event handlers are implemented in this object
	 * @return the parsed components' root
	 * @throws java.io.IOException
	 */
	public Object parse(InputStream inputstream, Object handler)
			throws IOException {
		return parse(inputstream, true, false, handler);
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Creates a component (and its subcomponents, and properties) from the
	 * given xml resource
	 * 
	 * @param path
	 *            is relative to your thinlet instance or the classpath (if the
	 *            path starts with an <i>/ </i> character), or a full URL
	 * @return the root component of the parsed resource
	 * @throws java.io.IOException
	 */
	public Object parse(String path) throws IOException {
		return parse(path, this);
	}

	/**
	 * Creates a component from the given xml resource using the specified event
	 * handler
	 * 
	 * @param path
	 *            is relative to your application package or the classpath, or
	 *            an URL
	 * @param handler
	 *            bussiness methods are implemented in this object
	 * @return the parsed components' root
	 * @throws java.io.IOException
	 */
	public Object parse(String path, Object handler) throws IOException {
		InputStream inputstream = null;
		try {
			inputstream = getClass().getResourceAsStream(path);
			if (inputstream == null) {
				//try {
				//    inputstream = new URL(path).openStream();
				//} catch (MalformedURLException mfe) { /*
				//                                       * thows
				//                                       * nullpointerexception
				//                                       */
				//}
			}
		} catch (Throwable e) {
		}
		return parse(inputstream, handler);
	}

	/**
	 * You can use the internal xml parser as a simple DOM-like parser, use the
	 * <i>getDOMAttribute </i>, <i>getDOMText </i>, <i>getDOMCount </i>,
	 * <i>getDOMNode </i>, <i>getClass </i>, and <i>getParent </i> methods to
	 * analise the document
	 * 
	 * @param inputstream
	 *            e.g. <i>new URL("http://myserver/myservlet").openStream() </i>
	 * @return the root tag
	 * @throws java.io.IOException
	 */
	protected Object parseDOM(InputStream inputstream) throws IOException {
		return parse(inputstream, false, true, null);
	}

	/**
	 * You can use the internal xml parser as a simple SAX-like parser, during
	 * the process it calls the <i>startElement </i>, <i>characters </i>, and
	 * <i>endElement </i> methods
	 * 
	 * @param inputstream
	 *            e.g. <i>new URL("http://myserver/myservlet").openStream() </i>
	 * @throws java.io.IOException
	 */
	protected void parseXML(InputStream inputstream) throws IOException {
		parse(inputstream, false, false, null);
	}

	protected void pointerDragged(int x, int y) {
		Object previnside = mouseinside;
		Object prevpart = insidepart;
		findComponent(content, x, y);
		boolean same = (previnside == mouseinside) && (prevpart == insidepart);
		boolean isin = (mousepressed == mouseinside)
				&& (pressedpart == insidepart);
		boolean wasin = (mousepressed == previnside)
				&& (pressedpart == prevpart);

		if (wasin && !isin) {
			handleMouseEvent(x, y, 1, false, false, false, MOUSE_EXITED,
					mousepressed, pressedpart);
		} else if (!same && (popupowner != null) && !wasin) {
			handleMouseEvent(x, y, 1, false, false, false, DRAG_EXITED,
					previnside, prevpart);
		}
		if (isin && !wasin) {
			handleMouseEvent(x, y, 1, false, false, false, MOUSE_ENTERED,
					mousepressed, pressedpart);
		} else if (!same && (popupowner != null) && !isin) {
			handleMouseEvent(x, y, 1, false, false, false, DRAG_ENTERED,
					mouseinside, insidepart);
		}
		if (isin == wasin) {
			handleMouseEvent(x, y, 1, false, false, false, MOUSE_DRAGGED,
					mousepressed, pressedpart);
		}
	}

	protected void pointerPressed(int x, int y) {
		findComponent(content, x, y);
		if (popupowner != null) {
			String classname = getClass(mouseinside);
			if ((popupowner != mouseinside) && (classname != ":popup")
					&& (classname != ":combolist")) {
				closeup();
			}
		}
		handleMouseEvent(x, y, 1, false, false, false, MOUSE_ENTERED,
				mouseinside, insidepart);
		mousepressed = mouseinside;
		pressedpart = insidepart;
		handleMouseEvent(x, y, 1, false, false, false, MOUSE_PRESSED,
				mousepressed, pressedpart);
	}

	protected void pointerReleased(int x, int y) {
		Object mouserelease = mousepressed;
		Object releasepart = pressedpart;
		mousepressed = pressedpart = null;
		handleMouseEvent(x, y, 1, false, false, false, MOUSE_RELEASED,
				mouserelease, releasepart);
	}

	/**
	 * Lays out a popupmenu
	 * 
	 * @param menu
	 *            menubar's menu, menu's menu, or component's popupmenu
	 *            including items
	 * @param popup
	 *            created popupmenu
	 * @param direction
	 *            'U' for up, 'D' for down, and 'R' for right
	 * @param x
	 *            menu's x location relative to the desktop
	 * @param y
	 *            menu's y location
	 * @param width
	 *            menu's width, or zero for popupmenu
	 * @param height
	 *            menu's height
	 * @param offset
	 *            inner padding relative to the menu's bounds
	 */
	private void popup(Object menu, Object popup, char direction, int x, int y,
			int width, int height, int offset) {
		int pw = 0;
		int ph = 0;
		for (Object item = get(menu, ":comp"); item != null; item = get(item,
				":next")) {
			String itemclass = getClass(item);
			Dimension d = (itemclass == "separator") ? new Dimension(1, 1)
					: getSize(item, 8, 4);
			if (itemclass == "checkboxmenuitem") {
				d.width = d.width + block + 3;
				d.height = Math.max(block, d.height);
			} else if (itemclass == "menu") {
				d.width += block;
			}
			String accelerator = getAccelerator(item); // add accelerator width
			if (accelerator != null) {
				d.width += 4 + font.stringWidth(accelerator); //TODO
				// font,
				// height
				// and gap
			}
			setRectangle(item, "bounds", 1, 1 + ph, d.width, d.height);
			pw = Math.max(pw, d.width);
			ph += d.height;
		}
		pw += 2;
		ph += 2; // add border widths
		// set :popup bounds
		Rectangle desktop = getRectangle(content, "bounds");
		if (direction == 'R') {
			x += ((x + width - offset + pw > desktop.width) && (x >= pw
					- offset)) ? (offset - pw) : (width - offset);
			if ((y + ph > desktop.height) && (ph <= y + height)) {
				y -= ph - height;
			}
		} else {
			boolean topspace = (y >= ph - offset); // sufficient space above
			boolean bottomspace = (desktop.height - y - height >= ph - offset);
			y += ((direction == 'U') ? (topspace || !bottomspace)
					: (!bottomspace && topspace)) ? (offset - ph)
					: (height - offset);
		}
		setRectangle(popup, "bounds", Math.max(0, Math.min(x, desktop.width
				- pw)), Math.max(0, Math.min(y, desktop.height - ph)), pw, ph);
		repaint(popup);
	}

	/**
	 * Pop up the list of choices for the given combobox
	 * 
	 * @param combobox
	 * @return the created combolist
	 */
	private Object popupCombo(Object combobox) {
		// combobox bounds relative to the root desktop
		int combox = 0, comboy = 0, combowidth = 0, comboheight = 0;
		for (Object comp = combobox; comp != content; comp = getParent(comp)) {
			Rectangle r = getRectangle(comp, "bounds");
			combox += r.x;
			comboy += r.y;
			Rectangle view = getRectangle(comp, ":view");
			if (view != null) {
				combox -= view.x;
				comboy -= view.y;
				Rectangle port = getRectangle(comp, ":port");
				combox += port.x;
				comboy += port.y;
			}
			if (comp == combobox) {
				combowidth = r.width;
				comboheight = r.height;
			}
		}
		// :combolist -> combobox and combobox -> :combolist
		Object combolist = createImpl(":combolist");
		set(combolist, "combobox", combobox);
		set(combobox, ":combolist", combolist);
		// add :combolist to the root desktop and set the combobox as popupowner
		popupowner = combobox;
		insertItem(content, ":comp", combolist, 0);
		set(combolist, ":parent", content);
		// lay out choices verticaly and calculate max width and height sum
		int pw = 0;
		int ph = 0;
		for (Object item = get(combobox, ":comp"); item != null; item = get(
				item, ":next")) {
			Dimension d = getSize(item, 8, 4);
			setRectangle(item, "bounds", 0, ph, d.width, d.height);
			pw = Math.max(pw, d.width);
			ph += d.height;
		}
		// set :combolist bounds
		int listy = 0, listheight = 0;
		int bellow = getRectangle(content, "bounds").height - comboy
				- comboheight - 1;
		if ((ph + 2 > bellow) && (comboy - 1 > bellow)) { // popup above
			// combobox
			listy = Math.max(0, comboy - 1 - ph - 2);
			listheight = Math.min(comboy - 1, ph + 2);
		} else { // popup bellow combobox
			listy = comboy + comboheight + 1;
			listheight = Math.min(bellow, ph + 2);
		}
		setRectangle(combolist, "bounds", combox, listy, combowidth, listheight);
		layoutScroll(combolist, pw, ph, 0, 0, 0, 0, true, 0);
		repaint(combolist);
		// hover the selected item
		int selected = getInteger(combobox, "selected", -1);
		setInside(combolist, (selected != -1) ? getItem(combobox, selected)
				: null, true);
		return combolist;
	}

	/**
	 * @param component
	 *            menubar or :popup
	 * @return the created popupmenu
	 */
	private Object popupMenu(Object component) {
		Object popup = get(component, ":popup"); // first :popup child
		Object selected = get(component, "selected"); // selected menu in of the
		// component
		if (popup != null) { // remove its current :popup
			if (get(popup, "menu") == selected) {
				return null;
			} // but the currect one
			set(popup, "selected", null);
			set(popup, "menu", null);
			repaint(popup);
			removeItemImpl(content, popup);
			set(popup, ":parent", null);
			set(component, ":popup", null);
			if (mouseinside == popup) {
				checkLocation();
			}
			popupMenu(popup); // remove recursively
		}
		// pop up the selected menu only
		if ((selected == null) || (getClass(selected) != "menu")) {
			return null;
		}
		// create the :popup, :popup.menu -> menu,
		// menubar|:popup.:popup -> :popup, menubar|:popup.selected -> menu
		popup = createImpl(":popup");
		set(popup, "menu", selected);
		set(component, ":popup", popup);
		insertItem(content, ":comp", popup, 0);
		set(popup, ":parent", content);
		// calculates the bounds of the previous menubar/:popup relative to the
		// root
		// desktop
		int menux = 0, menuy = 0, menuwidth = 0, menuheight = 0;
		for (Object comp = component; comp != content; comp = getParent(comp)) {
			Rectangle r = getRectangle(comp, "bounds");
			menux += r.x;
			menuy += r.y;
			Rectangle view = getRectangle(comp, ":view");
			if (view != null) {
				menux -= view.x;
				menuy -= view.y;
				Rectangle port = getRectangle(comp, ":port");
				menux += port.x;
				menuy += port.y;
			}
			if (comp == component) {
				menuwidth = r.width;
				menuheight = r.height;
			}
		}
		// set :popup bounds
		Rectangle menubounds = getRectangle(selected, "bounds");
		boolean menubar = ("menubar" == getClass(component));
		if (menubar) {
			popupowner = component;
		}
		popup(selected, popup, menubar ? (("bottom" != get(component,
				"placement")) ? 'D' : 'U') : 'R',
				menubar ? (menux + menubounds.x) : menux, menuy + menubounds.y,
				menubar ? menubounds.width : menuwidth, menubar ? menuheight
						: menubounds.height, menubar ? 1 : 3);
		return popup;
	}

	/**
	 * @param popupmenu
	 */
	private void popupPopup(Object popupmenu, int x, int y) {
		// :popup.menu -> popupmenu, popupmenu.:popup -> :popup
		Object popup = createImpl(":popup");
		set(popup, "menu", popupmenu);
		set(popupmenu, ":popup", popup);
		// add :popup to the root desktop and set the combobox as popupowner
		popupowner = popupmenu;
		insertItem(content, ":comp", popup, 0);
		set(popup, ":parent", content);
		// lay out
		popup(popupmenu, popup, 'D', x, y, 0, 0, 0);
		// invoke menushown listener
		invoke(popupmenu, null, "menushown"); // TODO before
	}

	/**
	 *  
	 */
	private void processField(int x, int y, int clickcount, int id,
			Object component, boolean multiline, boolean hidden, int left) {
		if (id == MOUSE_PRESSED) {
			//+ middle=alt paste clipboard content
			setReference(component, 2 + left, 2);
			int mx = x - referencex;
			int my = 0;
			if (!multiline) {
				mx += getInteger(component, ":offset", 0);
			} else {
				Rectangle port = getRectangle(component, ":port");
				mx += port.x - 1;
				my = y - referencey + port.y - 1;
			}
			int caretstart = getCaretLocation(component, mx, my, multiline,
					hidden);
			int caretend = caretstart;
			/*
			 * if (clickcount > 1) { String text = getString(component, "text",
			 * ""); while ((caretstart > 0) && ((clickcount == 2) ?
			 * Character.isLetterOrDigit(text.charAt(caretstart - 1)) : (text
			 * .charAt(caretstart - 1) != '\n'))) { caretstart--; } while
			 * ((caretend < text.length()) && ((clickcount == 2) ?
			 * Character.isLetterOrDigit(text.charAt(caretend)) : (text
			 * .charAt(caretend) != '\n'))) { caretend++; } }
			 */
			setInteger(component, "start", caretstart, 0);
			setInteger(component, "end", caretend, 0);
			setFocus(component);
			validate(component); // caret check only
		} else if (id == MOUSE_DRAGGED) {
			int mx = x - referencex;
			int my = 0;
			if (!multiline) {
				mx += getInteger(component, ":offset", 0);
			} else {
				Rectangle port = getRectangle(component, ":port");
				mx += port.x - 1;
				my = y - referencey + port.y - 1;
			}
			int dragcaret = getCaretLocation(component, mx, my, multiline,
					hidden);
			if (dragcaret != getInteger(component, "end", 0)) {
				setInteger(component, "end", dragcaret, 0);
				validate(component); // caret check only
			}
			/*
			 * } else if ((id == MOUSE_ENTERED) && (mousepressed == null)) {
			 * setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR)); } else
			 * if (((id == MOUSE_EXITED) && (mousepressed == null)) || ((id ==
			 * MOUSE_RELEASED) && ((mouseinside != component) || (insidepart !=
			 * null)))) {
			 * setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			 */
		}
	}

	/**
	 * Process keyboard events for textfield, passwordfield, textarea, combobox,
	 * and spinbox
	 * 
	 * @param multiline
	 *            true for textarea, otherwise false
	 * @param hidden
	 *            true for passwordfield, otherwise false
	 * @param filter
	 *            true for spinbox, otherwise false
	 */
	private boolean processField(Object component, boolean shiftdown,
			boolean controldown, int modifiers, int keychar, int keycode,
			boolean multiline, boolean hidden, boolean filter) {
		String text = getString(component, "text", "");
		int start = getInteger(component, "start", 0);
		int end = getInteger(component, "end", 0);
		boolean editable = getBoolean(component, "editable", true);

		int istart = start;
		int iend = end;
		String insert = null;
		if (editable && (keychar != 0)) {
			//((modifiers == 0) || (modifiers == InputEvent.SHIFT_MASK))) {
			//(modifiers != InputEvent.ALT_MASK)) {
			insert = String.valueOf((char) keychar);
		} else if (editable && (keycode == KeyEvent.VK_ENTER)) {
			if (multiline) {
				insert = "\n";
			} else {
				return invoke(component, null, "perform");
			}
		} else if (editable && (keycode == KeyEvent.VK_BACK_SPACE)) {
			insert = "";
			if (start == end) {
				istart -= 1;
			}
		} else if (keycode == KeyEvent.VK_END) {
			iend = text.length();
			if (!shiftdown) {
				istart = iend;
			}
		} else if (keycode == KeyEvent.VK_HOME) {
			iend = 0;
			if (!shiftdown) {
				istart = iend;
			}
		} else if (keycode == KeyEvent.VK_LEFT) {
			/*
			 * if (controldown) { for (int i = 0; i < 2; i++) { while ((iend >
			 * 0) && ((i != 0) == Character.isLetterOrDigit(text.charAt(iend -
			 * 1)))) { iend--; } } } else { iend -= 1; }
			 */
			iend -= 1;
			if (!shiftdown) {
				istart = iend;
			}
		} else if (keycode == KeyEvent.VK_RIGHT) {
			/*
			 * if (controldown) { for (int i = 0; i < 2; i++) { while ((iend <
			 * text.length()) && ((i == 0) ==
			 * Character.isLetterOrDigit(text.charAt(iend)))) { iend++; } } }
			 * else { iend += 1; }
			 */
			iend += 1;
			if (!shiftdown) {
				istart = iend;
			}
		} else if (editable && (keycode == KeyEvent.VK_DELETE)) {
			insert = "";
			if (start == end) {
				iend += 1;
			}
		} else if (controldown
				&& ((keycode == KeyEvent.VK_A) || (keycode == 0xBF))) {
			istart = 0; // KeyEvent.VK_SLASH
			iend = text.length();
		} else if (controldown && (keycode == 0xDC)) {
			istart = iend = text.length(); // KeyEvent.VK_BACK_SLASH
		} else if ((editable && !hidden && controldown && (keycode == KeyEvent.VK_X))
				|| (!hidden && controldown && (keycode == KeyEvent.VK_C))) {
			if (start != end) {
				clipboard = text.substring(Math.min(start, end), Math.max(
						start, end));
				/*
				 * try { getToolkit().getSystemClipboard().setContents(new
				 * StringSelection(clipboard), null); } catch (Exception exc) { }
				 */
				if (keycode == KeyEvent.VK_X) {
					insert = "";
				} else {
					return true;
				}
			}
		} else if (editable && controldown && (keycode == KeyEvent.VK_V)) {
			/*
			 * try { insert = (String)
			 * getToolkit().getSystemClipboard().getContents(this).getTransferData(
			 * DataFlavor.stringFlavor); } catch (Exception exc) { insert =
			 * clipboard; }
			 */
			insert = clipboard;
			if (insert != null) { // no text on system clipboard nor internal
				// clipboard text
				insert = filter(insert, multiline);
			}
		}
		if (filter && (insert != null)) { // contributed by Michael Nascimento
			for (int i = insert.length() - 1; i >= 0; i--) {
				if (!Character.isDigit(insert.charAt(i))) {
					return false;
				}
			}
		}
		return changeField(component, text, insert, istart, iend, start, end);
	}

	/**
	 *  
	 */
	protected boolean processKeyPress(Object component, boolean shiftdown,
			boolean controldown, int modifiers, int keychar, int keycode) {
		String classname = getClass(component);

		if ("table" == classname) {
			if (keycode == 0x25 || keycode == 0x27) {
				if (keycode == 0x25) {
					return processScroll(component, "left");
				} else if (keycode == 0x27) {
					return processScroll(component, "right");
				}
			}
		} else {

		}

		if ("button" == classname) {
			if (keychar == KeyEvent.VK_SPACE
					|| ((keycode == KeyEvent.VK_ENTER) && (get(component,
							"type") == "default"))
					|| ((keycode == KeyEvent.VK_ESCAPE) && //...
					(get(component, "type") == "cancel"))) {
				//pressedkey = keychar;
				invoke(component, null, "action");
				repaint(component);
				return true;
			}
		} else if (("checkbox" == classname) || ("togglebutton" == classname)) {
			if (keychar == KeyEvent.VK_SPACE) {
				changeCheck(component, true);
				repaint(component);
				return true;
			}
		} else if ("combobox" == classname) {
			Object combolist = get(component, ":combolist");
			if (combolist == null) { // the drop down list is not visible
				boolean editable = getBoolean(component, "editable", true);
				if (editable
						&& processField(component, shiftdown, controldown,
								modifiers, keychar, keycode, false, false,
								false)) {
					setInteger(component, "selected", -1, -1);
					return true;
				}
				if ((keychar == KeyEvent.VK_SPACE)
						|| (keycode == KeyEvent.VK_DOWN)) {
					popupCombo(component);
				}
				//+findText
				else
					return false;
			} else {
				if ((keycode == KeyEvent.VK_UP)
						|| (keycode == KeyEvent.VK_DOWN)
						|| (keycode == KeyEvent.VK_PAGE_UP)
						|| (keycode == KeyEvent.VK_PAGE_DOWN)
						|| (keycode == KeyEvent.VK_HOME)
						|| (keycode == KeyEvent.VK_END)) {
					Object next = getListItem(component, combolist, keycode,
							get(combolist, ":lead"), false);
					if (next != null) {
						setInside(combolist, next, true);
					}
				} else if ((keycode == KeyEvent.VK_ENTER)
						|| (keychar == KeyEvent.VK_SPACE)) {
					closeCombo(component, combolist, get(combolist, ":lead")); //Alt+Up
				} else if (keycode == KeyEvent.VK_ESCAPE) {
					closeCombo(component, combolist, null);
				} else if (!processField(component, shiftdown, controldown,
						modifiers, keychar, keycode, false, false, false)) {
					Object item = findText((char) keychar, component,
							combolist, false);
					if (item != null) {
						setInside(combolist, item, true);
					} else
						return false;
				}
			}
			return true;
		} else if (("textfield" == classname) || ("passwordfield" == classname)) {

			return processField(component, shiftdown, controldown, modifiers,
					keychar, keycode, false, ("passwordfield" == classname),
					false);
		} else if ("textarea" == classname) {
			char[] chars = (char[]) get(component, ":text");
			int start = getInteger(component, "start", 0);
			int end = getInteger(component, "end", 0);

			int istart = start;
			int iend = end;
			String insert = null;
			if ((keycode == KeyEvent.VK_HOME) && !controldown) {
				while ((iend > 0) && (chars[iend - 1] != '\n')) {
					iend--;
				}
				if (!shiftdown) {
					istart = iend;
				}
			} else if ((keycode == KeyEvent.VK_END) && !controldown) {
				while ((iend < chars.length) && (chars[iend] != '\n')) {
					iend++;
				}
				if (!shiftdown) {
					istart = iend;
				}
			} else if ((keycode == KeyEvent.VK_UP)
					|| (keycode == KeyEvent.VK_PAGE_UP)
					|| (keycode == KeyEvent.VK_DOWN)
					|| (keycode == KeyEvent.VK_PAGE_DOWN)) {
				//SH
				//Font currentfont = (Font) get(component, "font");
				Font currentfont = font;
				if (currentfont == null)
					currentfont = font;
				int fh = currentfont.getHeight();
				int y = 0;
				int linestart = 0;
				for (int i = 0; i < iend; i++) {
					if ((chars[i] == '\n') || (chars[i] == '\t')) {
						linestart = i + 1;
						y += fh;
					}
				}
				if (keycode == KeyEvent.VK_UP) {
					y -= fh;
				} else if (keycode == KeyEvent.VK_DOWN) {
					y += fh;
				} else {
					int dy = getRectangle(component, ":port").height;
					y += (keycode == KeyEvent.VK_PAGE_UP) ? -dy : dy; // VK_PAGE_DOWN
				}
				int x = currentfont.charsWidth(chars, linestart, iend
						- linestart);
				iend = getCaretLocation(component, x, y, true, false);
				if (!shiftdown) {
					istart = iend;
				}
			} else
				return processField(component, shiftdown, controldown,
						modifiers, keychar, keycode, true, false, false);
			return changeField(component, getString(component, "text", ""),
					insert, istart, iend, start, end);
		} else if ("tabbedpane" == classname) {
			if ((keycode == KeyEvent.VK_RIGHT) || (keycode == KeyEvent.VK_DOWN)
					|| (keycode == KeyEvent.VK_LEFT)
					|| (keycode == KeyEvent.VK_UP)) {
				int selected = getInteger(component, "selected", 0);
				boolean increase = (keycode == KeyEvent.VK_RIGHT)
						|| (keycode == KeyEvent.VK_DOWN);
				int newvalue = selected;
				int n = increase ? getItemCountImpl(component, ":comp") : 0;
				int d = (increase ? 1 : -1);
				for (int i = selected + d; increase ? (i < n) : (i >= 0); i += d) {
					if (getBoolean(getItem(component, i), "enabled", true)) {
						newvalue = i;
						break;
					}
				}
				if (newvalue != selected) {
					setInteger(component, "selected", newvalue, 0);
					checkOffset(component);
					repaint(component);
					invoke(component, getItem(component, newvalue), "action");
				}
			}
		} else if ("spinbox" == classname) {
			if ((keycode == KeyEvent.VK_UP) || (keycode == KeyEvent.VK_DOWN)) {
				processSpin(component, (keycode == KeyEvent.VK_UP) ? "up"
						: "down");
				return true;
			}
			return processField(component, shiftdown, controldown, modifiers,
					keychar, keycode, false, false, true);
		} else if ("slider" == classname) {
			int value = getInteger(component, "value", 0);
			int d = 0;
			if ((keycode == KeyEvent.VK_HOME) || (keycode == KeyEvent.VK_LEFT)
					|| (keycode == KeyEvent.VK_UP)
					|| (keycode == KeyEvent.VK_PAGE_UP)) {
				d = getInteger(component, "minimum", 0) - value;
				if ((keycode == KeyEvent.VK_LEFT)
						|| (keycode == KeyEvent.VK_UP)) {
					d = Math.max(d, -getInteger(component, "unit", 5));
				} else if (keycode == KeyEvent.VK_PAGE_UP) {
					d = Math.max(d, -getInteger(component, "block", 25));
				}
			} else if ((keycode == KeyEvent.VK_END)
					|| (keycode == KeyEvent.VK_RIGHT)
					|| (keycode == KeyEvent.VK_DOWN)
					|| (keycode == KeyEvent.VK_PAGE_DOWN)) {
				d = getInteger(component, "maximum", 100) - value;
				if ((keycode == KeyEvent.VK_RIGHT)
						|| (keycode == KeyEvent.VK_DOWN)) {
					d = Math.min(d, getInteger(component, "unit", 5));
				} else if (keycode == KeyEvent.VK_PAGE_DOWN) {
					d = Math.min(d, getInteger(component, "block", 25));
				}
			}
			if (d != 0) {
				setInteger(component, "value", value + d, 0);
				repaint(component);
				invoke(component, null, "action");
			}
		} else if ("splitpane" == classname) {
			int divider = getInteger(component, "divider", -1);
			int d = 0;
			if (keycode == KeyEvent.VK_HOME) {
				d = -divider;
			} else if ((keycode == KeyEvent.VK_LEFT)
					|| (keycode == KeyEvent.VK_UP)) {
				d = Math.max(-10, -divider);
			} else if ((keycode == KeyEvent.VK_END)
					|| (keycode == KeyEvent.VK_RIGHT)
					|| (keycode == KeyEvent.VK_DOWN)) {
				boolean horizontal = ("vertical" != get(component,
						"orientation"));
				Rectangle bounds = getRectangle(component, "bounds");
				int max = (horizontal ? bounds.width : bounds.height) - 5;
				d = max - divider;
				if (keycode != KeyEvent.VK_END) {
					d = Math.min(d, 10);
				}
			}
			if (d != 0) {
				setInteger(component, "divider", divider + d, -1);
				validate(component);
			}
		} else if (("list" == classname) || ("table" == classname)) {
			return processList(component, shiftdown, controldown, keychar,
					keycode, false);
		} else if ("tree" == classname) {
			//? clear childs' selection, select this is its subnode was
			// selected
			if (keycode == KeyEvent.VK_LEFT) {
				Object lead = get(component, ":lead");
				if ((get(lead, ":comp") != null)
						&& getBoolean(lead, "expanded", true)) { // collapse
					setBoolean(lead, "expanded", false, true);
					selectItem(component, lead, true);
					validate(component);
					invoke(component, lead, "collapse"); //lead
					return true;
				} else { // select parent
					Object parent = getParent(lead);
					if (parent != component) {
						selectItem(component, parent, true);
						setLead(component, lead, parent);
						return true;
					}
				}
			}
			//? for interval mode select its all subnode or deselect all after
			else if (keycode == KeyEvent.VK_RIGHT) {
				Object lead = get(component, ":lead");
				Object node = get(lead, ":comp");
				if (node != null) {
					if (getBoolean(lead, "expanded", true)) { // select its
						// first subnode
						selectItem(component, node, true);
						setLead(component, lead, node);
					} else { // expand
						setBoolean(lead, "expanded", true, true);
						selectItem(component, lead, true);
						validate(component);
						invoke(component, lead, "expand"); //lead
					}
					return true;
				}
			}
			return processList(component, shiftdown, controldown, keychar,
					keycode, true);
		} else if (("menubar" == classname) || ("popupmenu" == classname)) {
			// find the last open :popup and the previous one
			Object previous = null;
			Object last = null;
			for (Object i = get(component, ":popup"); i != null; i = get(i,
					":popup")) {
				previous = last;
				last = i;
			}
			//selected is the current item of the last, or the previous :popup,
			// or
			// null
			Object selected = get(last, "selected");
			Object hotpopup = ((selected != null) || (previous == null)) ? last
					: previous;
			if ((selected == null) && (previous != null)) {
				selected = get(previous, "selected");
			}

			if ((keycode == KeyEvent.VK_UP) || (keycode == KeyEvent.VK_DOWN)) {
				Object next = getMenu(hotpopup, selected,
						keycode == KeyEvent.VK_DOWN, true);
				if (next != null) {
					set(hotpopup, "selected", null);
					popupMenu(hotpopup);
					set(hotpopup, "selected", next);
					repaint(hotpopup);
				}
			} else if (keycode == KeyEvent.VK_LEFT) {
				if (previous != null) { // close the last :popup
					selected = get(previous, "selected");
					set(previous, "selected", null);
					popupMenu(previous);
					set(previous, "selected", selected);
					repaint(previous); // , selected
				} else if ("menubar" == classname) { // select the previous
					// menubar menu
					Object next = getMenu(component,
							get(component, "selected"), false, false);
					if (next != null) {
						set(component, "selected", next);
						Object popup = popupMenu(component);
						set(popup, "selected", getMenu(popup, null, true, true));
						repaint(component); // , selected
					}
				}
			} else if (keycode == KeyEvent.VK_RIGHT) {
				if ((previous != null) && (selected == null)) { // ?
					set(last, "selected", get(get(last, "menu"), ":comp"));
					repaint(last); // , selected
				} else if ((selected != null) && (getClass(selected) == "menu")) { // expand
					// menu
					Object popup = popupMenu(last);
					set(popup, "selected", getMenu(popup, null, true, true));
				} else if ("menubar" == classname) { // select the next menubar
					// menu
					Object next = getMenu(component,
							get(component, "selected"), true, false);
					if (next != null) {
						set(component, "selected", next);
						Object popup = popupMenu(component);
						set(popup, "selected", getMenu(popup, null, true, true));
						repaint(component); // , selected
					}
				}
			} else if ((keycode == KeyEvent.VK_ENTER)
					|| (keychar == KeyEvent.VK_SPACE)
					|| (keycode == KeyEvent.VK_ESCAPE)) {
				if ((keycode != KeyEvent.VK_ESCAPE)
						&& getBoolean(selected, "enabled", true)) {
					if ((selected != null)
							&& (getClass(selected) == "checkboxmenuitem")) {
						changeCheck(selected, false);
					} else
						invoke(selected, null, "action");
				}
				closeup();
			} else
				return false;
			return true;
		}
		return false;
	}

	/**
	 *  
	 */
	public boolean processList(Object component, boolean shiftdown,
			boolean controldown, int keychar, int keycode, boolean recursive) {
		if ((keycode == KeyEvent.VK_UP) || // select previous/next/first/...
				// item
				(keycode == KeyEvent.VK_DOWN)
				|| (keycode == KeyEvent.VK_PAGE_UP)
				|| (keycode == KeyEvent.VK_PAGE_DOWN)
				|| (keycode == KeyEvent.VK_HOME)
				|| (keycode == KeyEvent.VK_END)) {
			Object lead = get(component, ":lead");
			if (getIndex(component, lead) == -1) {
				set(component, ":lead", lead = null);
			}
			Object row = getListItem(component, component, keycode, lead,
					recursive);
			if (row != null) {
				String selection = getString(component, "selection", "single");
				if (shiftdown && (selection != "single") && (lead != null)) {
					extend(component, lead, row, recursive);
				} else if (!controldown) {
					selectItem(component, row, recursive);
				}
				setLead(component, lead, row);
				return true;
			}
		} else if (keycode == KeyEvent.VK_LEFT) {
			return processScroll(component, "left");
		} else if (keycode == KeyEvent.VK_RIGHT) {
			return processScroll(component, "right");
		} else if (keycode == KeyEvent.VK_ENTER) {
			invoke(component, null, "perform");
		} else if (keychar == KeyEvent.VK_SPACE) { // select the current item
			select(component, get(component, ":lead"), recursive, shiftdown,
					controldown); //...
			return true;
		} else if (controldown) {
			if (((keycode == KeyEvent.VK_A) || (keycode == 0xBF)) && //KeyEvent.VK_SLASH
					(getString(component, "selection", "single") != "single")) { // select
				// all
				selectAll(component, true, recursive);
				return true;
			} else if (keycode == 0xDC) { //KeyEvent.VK_BACK_SLASH // deselect
				// all
				selectAll(component, false, recursive);
				return true;
			}
		} else {
			Object item = findText((char) keychar, component, component,
					recursive);
			if (item != null) {
				select(component, item, recursive, false, false);
				return true;
			}
		}
		return false;
	}

	/**
	 *  
	 */
	private boolean processScroll(int x, int y, int id, Object component,
			Object part) {
		if ((part == "up") || (part == "down") || (part == "left")
				|| (part == "right")) {
			if ((id == MOUSE_ENTERED) || (id == MOUSE_EXITED)
					|| (id == MOUSE_PRESSED) || (id == MOUSE_RELEASED)) {
				if (id == MOUSE_PRESSED) {
					if (processScroll(component, part)) {
						setTimer(300L);
						return true;
					}
				} else {
					if (id == MOUSE_RELEASED) {
						setTimer(0L);
					}
					repaint(component, null, part);
				}
			}
		} else if ((part == "uptrack") || (part == "downtrack")
				|| (part == "lefttrack") || (part == "righttrack")) {
			if (id == MOUSE_PRESSED) {
				if (processScroll(component, part)) {
					setTimer(300L);
				}
			} else if (id == MOUSE_RELEASED) {
				setTimer(0L);
			}
		} else if ((part == "vknob") || (part == "hknob")) {
			if (id == MOUSE_PRESSED) {
				Rectangle port = getRectangle(component, ":port");
				Rectangle view = getRectangle(component, ":view");
				if (part == "hknob") {
					referencex = x - view.x * (port.width - 2 * block)
							/ view.width;
				} else {
					referencey = y - view.y * (port.height - 2 * block)
							/ view.height;
				}
			} else if (id == MOUSE_DRAGGED) {
				Rectangle port = getRectangle(component, ":port");
				Rectangle view = getRectangle(component, ":view");
				if (part == "hknob") {
					int viewx = (x - referencex) * view.width
							/ (port.width - 2 * block);
					viewx = Math.max(0, Math
							.min(viewx, view.width - port.width));
					if (view.x != viewx) {
						view.x = viewx;
						repaint(component, null, "horizontal");
					}
				} else { // (part == "vknob")
					int viewy = (y - referencey) * view.height
							/ (port.height - 2 * block);
					viewy = Math.max(0, Math.min(viewy, view.height
							- port.height));
					if (view.y != viewy) {
						view.y = viewy;
						repaint(component, null, "vertical");
					}
				}
			}
		} else if (part == "corner") {
			part = "corner"; // compiler bug
		} else { //?
			if (id == MOUSE_PRESSED) {
				Rectangle port = getRectangle(component, ":port");
				if (port != null) {
					setReference(component, port.x, port.y);
				}
			}
			return false;
		}
		return true;
	}

	/**
	 *  
	 */
	public boolean processScroll(Object component, Object part) {
		Rectangle view = getRectangle(component, ":view");
		Rectangle port = ((part == "left") || (part == "up")) ? null
				: getRectangle(component, ":port");
		int dx = 0;
		int dy = 0;
		if (part == "left") {
			dx = -10;
		} else if (part == "lefttrack") {
			dx = -port.width;
		} else if (part == "right") {
			dx = 10;
		} else if (part == "righttrack") {
			dx = port.width;
		} else if (part == "up") {
			dy = -10;
		} else if (part == "uptrack") {
			dy = -port.height;
		} else if (part == "down") {
			dy = 10;
		} else if (part == "downtrack") {
			dy = port.height;
		}
		if (dx != 0) {
			dx = (dx < 0) ? Math.max(-view.x, dx) : Math.min(dx, view.width
					- port.width - view.x);
		} else if (dy != 0) {
			dy = (dy < 0) ? Math.max(-view.y, dy) : Math.min(dy, view.height
					- port.height - view.y);
		} else
			return false;
		if ((dx == 0) && (dy == 0)) {
			return false;
		}
		view.x += dx;
		view.y += dy;
		repaint(component, null, (dx != 0) ? "horizontal" : "vertical");
		return (((part == "left") || (part == "lefttrack")) && (view.x > 0))
				|| (((part == "right") || (part == "righttrack")) && (view.x < view.width
						- port.width))
				|| (((part == "up") || (part == "uptrack")) && (view.y > 0))
				|| (((part == "down") || (part == "downtrack")) && (view.y < view.height
						- port.height));
	}

	/**
	 *  
	 */
	private boolean processSpin(Object component, Object part) {
		String text = getString(component, "text", "");
		try {
			int itext = Integer.parseInt(text);
			int step = getInteger(component, "step", 1);
			if ((part == "up") ? (itext + step <= getInteger(component,
					"maximum", Integer.MAX_VALUE))
					: (itext - step >= getInteger(component, "minimum",
							Integer.MIN_VALUE))) {
				String value = String.valueOf((part == "up") ? (itext + step)
						: (itext - step));
				setString(component, "text", value, null);
				setInteger(component, "start", value.length(), 0);
				setInteger(component, "end", 0, 0);
				repaint(component, "spinbox", "text");
				invoke(component, null, "action");
				return true;
			}
		} catch (NumberFormatException nfe) {
		}
		return false;
	}

	/**
	 * Binds the specified key to the specified value, and stores in this
	 * component. <i>Null </i> value removes the property. Use the parameter tag
	 * in the xml resource to bind a string value, the format is:
	 * <i>parameter='key=value' </i>
	 * 
	 * @param component
	 *            the hashtable is binded to this component
	 * @param key
	 *            the client property key
	 * @param value
	 *            the new client property value
	 */
	public void putProperty(Object component, Object key, Object value) {
		Object table = get(component, ":bind");
		if (value != null) {
			if (table == null) {
				set(component, ":bind", table = new Hashtable());
			}
			((Hashtable) table).put(key, value);
		} else if (table != null) {
			((Hashtable) table).remove(key);
		}
	}

	/**
	 * Remove the specified component from its parent list, or delete
	 * component's popupmenu or table's header
	 * 
	 * @param component
	 *            the component to be removed
	 */
	public void remove(Object component) {
		update(component, "validate");
		Object parent = getParent(component);
		Object classname = getClass(component);
		if (("popupmenu" == classname) || ("header" == classname)) {
			set(parent, classname, null);
		} else {
			removeItemImpl(parent, component);
			// reuest focus for its parent if the component (or subcomponent) is
			// currently focused
			for (Object comp = focusowner; comp != null; comp = getParent(comp)) {
				if (comp == component) {
					setNextFocusable(parent, false);
					break;
				}
			}
		}
	}

	/**
	 * Removes all the components from this container's specified list
	 * 
	 * @param component
	 *            the specified container
	 */
	public void removeAll(Object component) {
		Object[] c = (Object[]) component;
		System.out.println("remove called for " + c[2]);
		System.out.println(component);
		if (get(component, ":comp") != null) {
			System.out.println("REMOVING!");
			set(component, ":comp", null);
			update(component, "validate");
		}
	}

	/**
	 * Delete the give component from its parent list
	 * 
	 * @param parent
	 * @param component
	 */
	private void removeItemImpl(Object parent, Object component) {
		Object previous = null; // the widget before the given component
		for (Object comp = get(parent, ":comp"); comp != null;) {
			Object next = get(comp, ":next");
			if (next == component) {
				previous = comp;
				break;
			}
			comp = next;
		}
		set((previous != null) ? previous : parent,
				(previous != null) ? ":next" : ":comp", get(component, ":next"));
		set(component, ":next", null);
		set(component, ":parent", null); // not required
	}

	/**
	 * Repaint the given component's area later
	 * 
	 * @param component
	 *            a visible widget inside thinlet desktop
	 */
	public void repaint(Object component) {
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds != null) {
			repaint(component, bounds.x, bounds.y, bounds.width, bounds.height);
		}
	}

	/**
	 * Repaint the given component's area later
	 * 
	 * @param component
	 * @param x
	 * @param y
	 * @param width
	 * @param height
	 */
	private void repaint(Object component, int x, int y, int width, int height) {
		while ((component = getParent(component)) != null) {
			Rectangle bounds = getRectangle(component, "bounds");
			x += bounds.x;
			y += bounds.y;
			Rectangle view = getRectangle(component, ":view");
			if (view != null) {
				Rectangle port = getRectangle(component, ":port");
				x += -view.x + port.x;
				y += -view.y + port.y; //+ clip :port
			}
		}
		repaint(x, y, width, height);
	}

	/**
	 *  
	 */
	private void repaint(Object component, Object classname, Object part) {
		Rectangle b = getRectangle(component, "bounds");
		if (classname == "combobox") { // combobox down arrow
			repaint(component, b.x + b.width - block, b.y, block, b.height); // icon?+
		} else if (classname == "spinbox") {
			if (part == "text") { // spinbox textfield content
				repaint(component, b.x, b.y, b.width - block, b.height);
			} else { // spinbox increase or decrease button
				repaint(component, b.x + b.width - block, (part == "up") ? b.y
						: (b.y + b.height - b.height / 2), block, b.height / 2);
			}
		}
		//else if (classname == "dialog") {}
		//int titleheight = getInteger(component, ":titleheight", 0);
		//else if (classname == "splitpane") {}
		else if ((classname == "tabbedpane") || // tab
				(classname == "menubar") || (classname == ":popup")) { // menuitem
			Rectangle r = getRectangle(part, "bounds");
			repaint(component, b.x + r.x, b.y + r.y,
					(classname == ":popup") ? b.width : r.width, r.height);
		}
		// classname: ":combolist" "textarea" "list" "table" "tree"
		else if ((part == "left") || (part == "right")) { // horizontal
			// scrollbar
			// button
			Rectangle r = getRectangle(component, ":horizontal");
			repaint(component, b.x
					+ ((part == "left") ? r.x : (r.x + r.width - block)), b.y
					+ r.y, block, r.height);
		} else if ((part == "up") || (part == "down")) { // vertical scrollbar
			// button
			Rectangle r = getRectangle(component, ":vertical");
			repaint(component, b.x + r.x, b.y
					+ ((part == "up") ? r.y : (r.y + r.height - block)),
					r.width, block);
		} else if ((part == "text") || (part == "horizontal")
				|| (part == "vertical")) {
			Rectangle port = getRectangle(component, ":port"); // textarea or
			// content
			repaint(component, b.x + port.x, b.y + port.y, port.width,
					port.height);
			if (part == "horizontal") {
				Rectangle r = getRectangle(component, ":horizontal");
				repaint(component, b.x + r.x, b.y + r.y, r.width, r.height);
				repaint(component, b.x + r.x, b.y, r.width, port.y); // paint
				// header
				// too
			} else if (part == "vertical") {
				Rectangle r = getRectangle(component, ":vertical");
				repaint(component, b.x + r.x, b.y + r.y, r.width, r.height);
			}
		} else { // repaint the whole line of its subcomponent
			Rectangle port = getRectangle(component, ":port");
			Rectangle view = getRectangle(component, ":view");
			Rectangle r = getRectangle(part, "bounds");
			if ((r.y + r.height >= view.y) && (r.y <= view.y + port.height)) {
				repaint(component, b.x + port.x, b.y + port.y - view.y + r.y,
						port.width, r.height);
				//? need cut item rectangle above/bellow viewport
			}
		}
	}

	/**
	 * Requests that both the <i>Thinlet </i> component, and the given widget
	 * get the input focus
	 * 
	 * @param component
	 *            a focusable widget inside visible and enabled parents, and
	 *            tabbedpane's selected tab
	 * @return true, if the given component was focusable
	 */
	public boolean requestFocus(Object component) { //#
		if (isFocusable(component, true)) {
			setFocus(component);
			repaint(component);
			return true;
		}
		return false;
	}

	/**
	 * A second thread is used to repeat value change events for scrollbar or
	 * spinbox during the mouse is pressed, or to pop up tooltip
	 */
	public synchronized void run() {
		while (timer == Thread.currentThread()) {
			try {
				if (watch == 0) {
					wait(0);
					hideTip();
				} else {
					long current = System.currentTimeMillis();
					if (watch > current) {
						wait(watch - current);
					} else {
						watch = 0;
						if ((watchdelay == 300L) || (watchdelay == 60L)) {
							if (processScroll(mousepressed, pressedpart)) {
								setTimer(60L);
							}
						} else if ((watchdelay == 375L) || (watchdelay == 75L)) {
							if (processSpin(mousepressed, pressedpart)) {
								setTimer(75L);
							}
						} else if (watchdelay == 750L) {
							showTip();
						}
					}
				}
			} catch (InterruptedException ie) {
			} //ie.printStackTrace();
		}
	}

	/**
	 *  
	 */
	private void scrollToVisible(Object component, int x, int y, int width,
			int height) {
		Rectangle view = getRectangle(component, ":view");
		Rectangle port = getRectangle(component, ":port");
		int vx = Math.max(x + width - port.width, Math.min(view.x, x));
		int vy = Math.max(y + height - port.height, Math.min(view.y, y));
		if ((view.x != vx) || (view.y != vy)) {
			repaint(component); // horizontal | vertical
			view.x = vx;
			view.y = vy;
		}
	}

	/**
	 *  
	 */
	private void select(Object component, Object row, boolean recursive,
			boolean shiftdown, boolean controldown) {
		String selection = getString(component, "selection", "single");
		Object lead = null;
		if (shiftdown && (selection != "single")
				&& ((lead = get(component, ":lead")) != null)) {
			extend(component, lead, row, recursive);
		} else {
			if (controldown && (selection == "multiple")) {
				setBoolean(row, "selected",
						!getBoolean(row, "selected", false), false);
				repaint(component, null, row);
				invoke(component, row, "action");
				set(component, ":anchor", null);
			} else if (controldown && getBoolean(row, "selected", false)) {
				for (Object item = row; item != null; item = getNextItem(
						component, item, recursive)) {
					if (setBoolean(item, "selected", false, false)) {
						repaint(component, null, item);
					}
				}
				invoke(component, row, "action");
				set(component, ":anchor", null);
			} else {
				selectItem(component, row, recursive);
			}
		}
		setLead(component, (lead != null) ? lead : get(component, ":lead"), row);
	}

	/**
	 * Select all the items
	 * 
	 * @param component
	 *            a list/tree/table
	 * @param selected
	 *            selects or deselects items
	 * @param recursive
	 *            true for tree
	 */
	private void selectAll(Object component, boolean selected, boolean recursive) {
		boolean changed = false;
		for (Object item = get(component, ":comp"); item != null; item = getNextItem(
				component, item, recursive)) {
			if (setBoolean(item, "selected", selected, false)) {
				repaint(component, null, item);
				changed = true;
			}
		}
		set(component, ":anchor", null);
		if (changed) {
			invoke(component, null, "action");
		}
	}

	/**
	 * Select a single given item, deselect others
	 * 
	 * @param component
	 *            a list/tree/table
	 * @param row
	 *            the item/node/row to select
	 * @param recursive
	 *            true for tree
	 */
	private void selectItem(Object component, Object row, boolean recursive) {
		boolean changed = false;
		for (Object item = get(component, ":comp"); item != null; item = getNextItem(
				component, item, recursive)) {
			if (setBoolean(item, "selected", (item == row), false)) {
				repaint(component, null, item);
				changed = true;
			}
		}
		set(component, ":anchor", null);
		if (changed) {
			invoke(component, row, "action");
		}
	}

	/**
	 * Sets the default behaviour of internationalization code. If set to
	 * "true", try to translate all components' "text" and "tooltip" values,
	 * unless explicitly prohibited by setting <code>i18n="false"</code> on a
	 * specific component. If set to "false", do not translate unless explicitly
	 * requested by setting <code>i18n="true"</code> on a specific component.
	 * <br />
	 * Default value is "false", to provide backwards compatibility.
	 * 
	 * @param val
	 *            if "true", translate by default; if "false", do not translate
	 *            by default.
	 */
	public void setAllI18n(boolean val) { // for I18N
		allI18n = val;
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setBoolean(Object component, String key, boolean value) {
		Object[] definition = getDefinition(getClass(component), key, "boolean");
		if (setBoolean(component, (String) definition[1], value,
				(definition[3] == TRUE))) {
			update(component, definition[2]);
		}
	}

	/**
	 *  
	 */
	private boolean setBoolean(Object component, String key, boolean value,
			boolean defaultvalue) {
		return set(component, key, (value == defaultvalue) ? null
				: (value ? TRUE : FALSE));
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setChoice(Object component, String key, String value) {
		Object[] definition = getDefinition(getClass(component), key, "choice");
		String[] values = (String[]) definition[3];
		if (setChoice(component, (String) definition[1], value, values,
				values[0])) {
			update(component, definition[2]);
		}
	}

	/**
	 * 
	 * @throws java.lang.IllegalArgumentException
	 */
	private boolean setChoice(Object component, String key, String value,
			String[] values, String defaultvalue) {
		if (value == null) {
			return set(component, key, defaultvalue);
		}
		for (int i = 0; i < values.length; i++) {
			if (value.equals(values[i])) {
				return set(component, key, values[i]);
			}
		}
		throw new IllegalArgumentException("unknown " + value + " for " + key);
	}

	/**
	 * Set custom color on a component. Notes: For "foreground" key, this sets
	 * the text color. For "background" key, on gradient-filled components (such
	 * as tabs, buttons etc) this will result in a component filled with solid
	 * background color, and not a new gradient. Also, Color.brighter() will be
	 * used for highlight, and Color.darker() will be used for pressed or not
	 * selected.
	 * 
	 * @param component
	 *            component to use for custom color
	 * @param key
	 *            currently "background" and "foreground" are supported
	 * @param color
	 *            custom color to use, or null to reset component to use default
	 *            color
	 */
	public void setColor(Object component, String key, Integer color) {
		Object[] definition = getDefinition(getClass(component), key, "color");
		if (set(component, definition[1], color)) {
			update(component, definition[2]);
		}
	}

	/**
	 * Sets the 9 colors used for components, and repaints the whole UI
	 * 
	 * @param background
	 *            the backround of panels (dialogs, desktops), and disabled
	 *            controls, not editable texts, lines between list items (the
	 *            default value if <i>#e6e6e6 </i>)
	 * @param text
	 *            for text, arrow foreground ( <i>black </i> by default)
	 * @param textbackground
	 *            the background of text components, and lists ( <i>white </i>
	 *            by default)
	 * @param border
	 *            for outer in inner borders of enabled components ( <i>#909090
	 *            </i> by default)
	 * @param disable
	 *            for text, border, arrow color in disabled components (
	 *            <i>#b0b0b0 </i> by default)
	 * @param hover
	 *            indicates that the mouse is inside a button area ( <i>#ededed
	 *            </i> by default)
	 * @param press
	 *            for pressed buttons, gradient image is calculated using the
	 *            background and this press color ( <i>#b9b9b9 </i> by default)
	 * @param focus
	 *            for text caret and rectagle color marking the focus owner (
	 *            <i>#89899a </i> by default)
	 * @param select
	 *            used as the background of selected text, and list items, and
	 *            in slider ( <i>#c5c5dd </i> by default)
	 */
	public void setColors(int background, int text, int textbackground,
			int border, int disable, int hover, int press, int focus, int select) {
		c_bg = (background);
		c_text = (text);
		c_textbg = (textbackground);
		c_border = (border);
		c_disable = (disable);
		c_hover = (hover);
		c_press = (press);
		c_focus = (focus);
		c_select = (select);
		c_ctrl = c_hover;
		repaint();
	}

	/**
	 * Request focus for the given component
	 * 
	 * @param component
	 *            a focusable component
	 * @return true if the focusowner was changed, otherwise false
	 */

	protected boolean setFocus(Object component) {
		// if (!focusinside) { // request focus for the thinlet component
		//     requestFocus();
		// }

		String classname = getClass(component);
		String focusownerClassname = getClass(focusowner);
		if ("textfield" == classname || "textarea" == classname
				|| "passwordfield" == classname) {
			if (focusowner != component) {
				addCommand(editCommand);

			}
		} else if ("textfield" == focusownerClassname
				|| "textarea" == focusownerClassname
				|| "passwordfield" == focusownerClassname) {

			removeCommand(editCommand);
		}

		if (focusowner != component) {

			Object focused = focusowner;
			if (focusowner != null) {
				focusowner = null; // clear focusowner
				repaint(focused);
				// invoke the focus listener of the previously focused component
				invoke(focused, null, "focuslost");
			}
			if (focusowner == null) { // it won't be null, if refocused
				focusowner = component;
				// invoke the focus listener of the new focused component
				invoke(component, null, "focusgained");
			}
			Object parent = getParent(focusowner);
			if (parent != null) {
				classname = getClass(parent);
				if ("panel" == classname && getBoolean(parent, "scrollable")) {
					Rectangle bounds = getRectangle(focusowner, "bounds");
					scrollToVisible(parent, bounds.x, bounds.y, bounds.width,
							bounds.height);
				}
			}
			return true;
		}
		return false;
	}

	//setDesktopProperty+

	/**
	 * Sets the only one font used everywhere, and revalidates the whole UI.
	 * Scrollbar width/height, spinbox, and combobox button width, and slider
	 * size is the same as the font height
	 * 
	 * @param font
	 *            the default font is <i>SansSerif </i>, <i>plain </i>, and
	 *            <i>12pt </i>
	 */
	public void setFont(Font font) {
		block = font.getHeight();

		this.font = font;
		if (content != null)
			validate(content);
	}

	/**
	 * Set custom font on a component, use the other <code>setFont</code>
	 * method instead
	 */
	public void setFont(Object component, Font font) { // deprecated
		setFont(component, "font", font);
	}

	/**
	 * Set custom font on a component
	 * 
	 * @param component
	 *            component to use the custom font
	 * @param font
	 *            custom font to use, or null to reset component to use default
	 *            font
	 */
	public void setFont(Object component, String key, Font font) {
		Object[] definition = getDefinition(getClass(component), key, "font");
		if (set(component, definition[1], font)) {
			update(component, definition[2]);
		}
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setIcon(Object component, String key, Image icon) {
		Object[] definition = getDefinition(getClass(component), key, "icon");
		if (set(component, definition[1], icon)) {
			update(component, definition[2]);
		}
	}

	/**
	 * Update the lead item of a combolist, repaint, and scroll
	 * 
	 * @param component
	 *            a combobox drop down list
	 * @param part
	 *            the current hotspot item
	 * @param scroll
	 *            scroll to the part if true
	 */
	private void setInside(Object component, Object part, boolean scroll) {
		Object previous = get(component, ":lead");
		if (previous != null) {
			repaint(component, ":combolist", previous);
		}
		set(component, ":lead", part);
		if (part != null) {
			repaint(component, ":combolist", part);
			if (scroll) {
				Rectangle r = getRectangle(part, "bounds");
				scrollToVisible(component, r.x, r.y, 0, r.height);
			}
		}
	}

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setInteger(Object component, String key, int value) {
		Object[] definition = getDefinition(getClass(component), key, "integer");
		if (setInteger(component, (String) definition[1], value,
				((Integer) definition[3]).intValue())) {
			update(component, definition[2]);
		}
	}

	/**
	 *  
	 */
	private boolean setInteger(Object component, String key, int value,
			int defaultvalue) {
		return set(component, key, (value == defaultvalue) ? null
				: new Integer(value));
	}

	/**
	 *  
	 */
	public void setKeystroke(Object component, String key, String value) {
		Object[] definition = getDefinition(getClass(component), key,
				"keystroke");
		// TODO check if changed
		setKeystrokeImpl(component, (String) definition[1], value);
		update(component, definition[2]);
	}

	/**
	 *  
	 */
	private void setKeystrokeImpl(Object component, String key, String value) {
		Long keystroke = null;
		if (value != null) {
			String token = value;
			try {
				int keycode = 0, modifiers = 0;
				StringTokenizer st = new StringTokenizer(value, " \r\n\t+");
				while (st.hasMoreTokens()) {
					token = st.nextToken().toUpperCase();
					//try {
					//    modifiers = modifiers | InputEvent.class.getField(token +
					// "_MASK").getInt(null);
					//} catch (Exception exc) { // not mask value
					//keycode = KeyEvent.class.getField("VK_" +
					// token).getInt(null);
					//}
				}
				keystroke = new Long(((long) modifiers) << 32 | keycode);
			} catch (Exception exc) {
				throw new IllegalArgumentException(token);
			}
		}
		set(component, key, keystroke);
	}

	/**
	 * Set current language resource bundle. This flushes all cached translated
	 * values, performs lazy loading of new values, and repaints the desktop.
	 * This implementation allows applications to switch language resources on
	 * the fly, without rebuilding/reloading components that use them. <br />
	 * The pseudo-code is as follows:
	 * 
	 * <ul>
	 * <li>if langResource and langResourceDefault are null, don't translate
	 * anything, no matter what other settings are. This behaviour provides
	 * compatibility with previous versions.</li>
	 * <li>if only langResourceDefault is set, use this when translation is
	 * required</li>
	 * <li>if allI18n is set to true:
	 * <ul>
	 * <li>if property "i18n" on a component is missing, or set to "true",
	 * translate</li>
	 * <li>if property "i18n" is present, and set to "false", do not translate
	 * </li>
	 * </ul>
	 * </li>
	 * <li>if allI18n is set to false:
	 * <ul>
	 * <li>if property "i18n" on a component is missing, or set to "false", do
	 * not translate</li>
	 * <li>if property "i18n" is present, and set to "true", translate</li>
	 * </ul>
	 * </li>
	 * </ul>
	 * 
	 * The "translate" step is applied only to values from "text" and "tooltip"
	 * properties (for now), and is applied as follows:
	 * 
	 * <ul>
	 * <li>use the value of "text" or "tooltip" as a lookup key</li>
	 * <li>use langResource to lookup the result value
	 * <ul>
	 * <li>if no value is found, use langResourceDefault for lookup
	 * <ul>
	 * <li>if no value is found, just return the original value of the
	 * property. Set a flag on component that prevents lookups in the future.
	 * This flag is cleared when langResource is changed.</li>
	 * </ul>
	 * </li>
	 * </ul>
	 * </li>
	 * <li>cache the result value, if any</li>
	 * </ul>
	 * 
	 * If translated value is found successfully, it is cached in the component.
	 * This cache is gradually flushed when setLangResource is called. Cached
	 * value is also flushed when setString() is called on a component.
	 * 
	 * @param res
	 *            resource bundle containing localized texts for "text" and
	 *            "tooltip"
	 */
	public void setLangResource(Hashtable res) { // for I18N
		langResource = res;
		doLayout(content);
		repaint(content);
	}

	/**
	 * Set default language resource bundle. Resources from this bundle will be
	 * used if they are missing in the current bundle.
	 * 
	 * @param res
	 *            resource bundle containing default localized texts for "text"
	 *            and "tooltip"
	 */
	public void setLangResourceDefault(Hashtable res) { // for I18N
		langResourceDefault = res;
		if (langResource == null)
			setLangResource(res);
	}

	/**
	 * Update the lead item of a list/tree/table, repaint, and scroll
	 * 
	 * @param component
	 *            a list, tree, or table
	 * @param oldlead
	 *            the current lead item
	 * @param lead
	 *            the new lead item
	 */
	private void setLead(Object component, Object oldlead, Object lead) {
		if (oldlead != lead) { //?
			if (oldlead != null) {
				repaint(component, null, oldlead);
			}
			set(component, ":lead", lead);
			repaint(component, null, lead);

			Rectangle r = getRectangle(lead, "bounds");
			scrollToVisible(component, r.x, r.y, 0, r.height);
		}
	}

	/**
	 * Sets a new event handler method for a component
	 * 
	 * @param component
	 *            the target component
	 * @param key
	 *            the key name of the parameter (e.g. <i>action </i>)
	 * @param value
	 *            the method name and parameters (e.g. <i>foo(this, this.text,
	 *            mybutton, mybutton.enabled) </i> for <i>public void foo(Object
	 *            component, String text, Object mybutton, boolean enabled)
	 *            </i>)
	 * @param root
	 *            the search starting component for name components in the
	 *            arguments
	 * @param handler
	 *            the target event handler object including the method
	 * @throws java.lang.IllegalArgumentException
	 */
	public void setMethod(Object component, String key, String value,
			Object root, Object handler) {
		key = (String) getDefinition(getClass(component), key, "method")[1];
		Object[] method = getMethod(component, value, root, handler);
		set(component, key, method);
	}

	/**
	 * @return next focusable component is found (not the first of the
	 *         desktop/dialog)
	 */
	private boolean setNextFocusable(Object current, boolean outgo) {
		boolean consumed = true;
		for (Object next = null, component = current; true; component = next) {
			next = get(component, ":comp"); // check first subcomponent
			if (next == null) {
				next = get(component, ":next");
			} // check next component
			while (next == null) { // find the next of the parents, or the
				// topmost
				component = getParent(component); // current is not on the
				// desktop
				if (component == null) {
					return false;
				}
				if ((component == content)
						|| ((getClass(component) == "dialog") && (!outgo || getBoolean(
								component, "modal", false)))) {
					consumed = false; // find next focusable but does not
					// consume event
					next = component; // the topmost (desktop or modal dialog)
				} else {
					next = get(component, ":next");
				}
			}
			if (next == current) {
				return false;
			} // one fucusable, no loop
			if (isFocusable(next, false)) {
				setFocus(next);
				return consumed;
			}
		}
	}

	/**
	 * @return previous focusable component is found (not the last of the
	 *         desktop/dialog)
	 */
	private boolean setPreviousFocusable(Object component, boolean outgo) {
		for (int i = 0; i < 2; i++) { // 0 is backward direction
			Object previous = getPreviousFocusable(component, null, true,
					false, (i == 0), outgo);
			if (previous != null) {
				setFocus(previous);
				return (i == 0);
			}
		}
		return false;
	}

	/**
	 *  
	 */
	private void setRectangle(Object component, String key, int x, int y,
			int width, int height) {
		Rectangle rectangle = getRectangle(component, key);
		String classname = getClass(component);

		if (rectangle != null) {
			rectangle.x = x;
			rectangle.y = y;
			rectangle.width = width;
			rectangle.height = height;
		} else {
			set(component, key, new Rectangle(x, y, width, height));
		}
	}

	/**
	 * Calculate the given point in a component relative to the thinlet desktop
	 * and set as reference value
	 * 
	 * @param component
	 *            a widget
	 * @param x
	 *            reference point relative to the component left edge
	 * @param y
	 *            relative to the top edge
	 */
	private void setReference(Object component, int x, int y) {
		referencex = x;
		referencey = y;
		for (; component != null; component = getParent(component)) {
			Rectangle bounds = getRectangle(component, "bounds");
			referencex += bounds.x;
			referencey += bounds.y;

			Rectangle port = getRectangle(component, ":port");
			if (port != null) { // content scrolled
				Rectangle view = getRectangle(component, ":view");
				referencex -= view.x - port.x;
				referencey -= view.y - port.y;
			}
		}
	}

	/**
	 * Set a bundle used in parse method, it replaces the parameter values
	 * starting with the 'i18n.' string with a value found in the given bundle
	 * 
	 * @param resourcebundle
	 *            a bundle for the next parsing or null to remove the current
	 *            one
	 * @throws MissingResourceException
	 *             if no object for the given key can be found
	 */
	public void setResourceBundle(Hashtable resourcebundle) {
		this.resourcebundle = resourcebundle;
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 * Sets the given property pair (key and value) for the component
	 */
	public void setString(Object component, String key, String value) {
		Object[] definition = getDefinition(getClass(component), key, "string");
		if (setString(component, (String) definition[1], value,
				(String) definition[3])) {
			update(component, definition[2]);
		}
	}

	// ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- ----- -----

	/**
	 *  
	 */
	private boolean setString(Object component, String key, String value,
			String defaultvalue) {
		if (allI18n && (langResource != null)
				&& ((key == "text") || (key == "tooltip"))) {
			putProperty(component, "i18n." + key, null); // for I18N
		}
		return set(component, key, value); // use defaultvalue
	}

	/**
	 *  
	 */
	private void setTimer(long delay) {
		watchdelay = delay;
		if (delay == 0) {
			watch = 0;
		} else {
			long prev = watch;
			watch = System.currentTimeMillis() + delay;
			if (timer == null) {
				timer = new Thread(this);
				timer.setPriority(Thread.MIN_PRIORITY);
				//timer.setDaemon(true);
				timer.start();
			}
			if ((prev == 0) || (watch < prev)) {
				synchronized (this) {
					notify();
				}
				//synchronized (this) { try { notify(); }catch
				// (IllegalMonitorStateException imse) {} }
			}
		}
	}

	protected void showNotify() {
		//System.out.println("showNotify");
		setRectangle(content, "bounds", 0, 0, getWidth(), getHeight());
		doLayout(content);
	}

	/**
	 *  
	 */
	private void showTip() {
		String text = null;
		tooltipowner = null;
		String classname = getClass(mouseinside);
		if ((classname == "tabbedpane") || (classname == "menubar")
				|| (classname == ":popup")) {
			if (insidepart != null) {
				text = getString(insidepart, "tooltip", null);
			}
		} else if (classname == ":combolist") {
			if (insidepart instanceof Object[]) {
				text = getString(insidepart, "tooltip", null);
			}
		}
		// TODO list table tree
		if (text == null) {
			text = getString(mouseinside, "tooltip", null);
		} else {
			tooltipowner = insidepart;
		}
		if (text != null) {
			int width = font.stringWidth(text) + 4;
			int height = font.getHeight() + 4;
			if (tooltipowner == null) {
				tooltipowner = mouseinside;
			}
			Rectangle bounds = getRectangle(content, "bounds");
			int tx = Math.max(0, Math.min(mousex + 10, bounds.width - width));
			int ty = Math.max(0, Math.min(mousey + 10, bounds.height - height));
			setRectangle(tooltipowner, ":tooltipbounds", tx, ty, width, height);
			repaint(tx, ty, width, height);
		}
	}

	protected void sizeChanged(int w, int h) {
		setInteger(content, "width", w);
		setInteger(content, "height", h);
		doLayout(content);
	}

	/**
	 * The SAX-like parser calls this method, you have to overwrite it
	 * 
	 * @param name
	 *            of the tag
	 * @param attributelist
	 *            a list of attributes including keys and value pairs
	 */
	protected void startElement(String name, Hashtable attributelist) {
	}

	/**
	 * Invokes the paint method
	 */
	public void update(Graphics g) {
		paint(g);
	}

	/**
	 *  
	 */
	private void update(Object component, Object mode) {
		if ("parent" == mode) {
			component = getParent(component);
			mode = "validate";
		}
		boolean firstpaint = true;
		int x = 0;
		int y = 0;
		int width = 0;
		int height = 0;
		while (component != null) {
			if (!getBoolean(component, "visible", true)) {
				break;
			}
			if ("paint" == mode) {//|| (firstpaint && (component == content))
				Rectangle bounds = getRectangle(component, "bounds");
				if (bounds == null) {
					return;
				}
				if (firstpaint) {
					x = bounds.x;
					y = bounds.y;
					width = Math.abs(bounds.width);
					height = bounds.height;
					firstpaint = false;
				} else {
					x += bounds.x;
					y += bounds.y;
				}
				if (component == content) {
					repaint(x, y, width, height);
				}
			}
			Object parent = getParent(component);
			String classname = getClass(parent);
			if ("combobox" == classname) {
				parent = get(parent, ":combolist");
			} else if ("menu" == classname) {
				parent = get(parent, ":popup");
			} else if (("paint" == mode) && ("tabbedpane" == classname)) {
				if (getItem(parent, getInteger(parent, "selected", 0)) != component) {
					break;
				}
			}
			if (("layout" == mode)
					|| (("validate" == mode) && (("list" == classname)
							|| ("table" == classname) || ("tree" == classname)
							|| ("dialog" == classname) || (parent == content)))) {
				Rectangle bounds = getRectangle(parent, "bounds");
				if (bounds == null) {
					return;
				}
				bounds.width = -1 * Math.abs(bounds.width);
				mode = "paint";
			}
			component = parent;
		}
	}

	/**
	 * Layout and paint the given component later
	 * 
	 * @param component
	 */
	private void validate(Object component) {
		repaint(component);
		Rectangle bounds = getRectangle(component, "bounds");
		if (bounds != null) {
			bounds.width = -1 * Math.abs(bounds.width);
		}
	}
}
